typora-root-url: images
一、概述
设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。
设计模式免去我们自己再思考和摸索。就像是经典的棋谱,不同的棋局,我们用不同的棋谱。”套路”
二、单例模式
2.1、概述
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例。并且该类只提供一个取得其对象实例的方法。如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为 private,这样,就不能用 new 操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。
2.2、饿汉式
2.2.1、代码实现
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
2.3、懒汉式
2.3.1、代码实现
public class Singleton {
private static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
2.3.2、说明
此种方式其实并不严谨,在多线程环境下会有问题,创建出来的实例对象可能不止一个。
2.3.3、解决
双重判断加锁实现。
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
2.4、单例模式应用场景
- 网站的计数器,一般也是单例模式实现,否则难以同步。
- 应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
- 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
- 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取。
- Application也是单例的典型应用
- Windows 的 Task Manager (任务管理器)就是很典型的单例模式
- Windows 的 Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
三、工厂模式
3.1、简单工厂模式/静态工厂模式
3.1.1、说明
一个抽象的接口,多个抽象接口的实现类,一个工厂类,用来实例化抽象的接口;
简单来说,假如现在有多个实现类C,D,E类,我们定义了一个A类(工厂类),我们可以通过向工厂类中传入适当的参数,返回需要的实现类(C,D,E)引用。
3.1.2、图示
3.1.3、代码实现
// 抽象产品类
interface Car {
public void run();
public void stop();
}
// 具体实现类
class Benz implements Car {
public void run() {
System.out.println("Benz开始启动了。。。。。");
}
public void stop() {
System.out.println("Benz停车了。。。。。");
}
}
class Ford implements Car {
public void run() {
System.out.println("Ford开始启动了。。。");
}
public void stop() {
System.out.println("Ford停车了。。。。");
}
}
// 工厂类
class Factory {
public static Car getCarInstance(String type) {
Car c = null;
if ("Benz".equals(type)) {
c = new Benz();
}
if ("Ford".equals(type)) {
c = new Ford();
}
return c;
}
}
public class Test {
public static void main(String[] args) {
Car c = Factory.getCarInstance("Benz");
if (c != null) {
c.run();
c.stop();
} else {
System.out.println("造不了这种汽车。。。");
}
}
}
对于简单的创建来说,简单工厂模式较为灵活方便,但耦合性较高,每添加一个新产品,都要重构代码。
3.2、工厂方法模式
3.2.1、说明
有四个角色,抽象工厂模式,具体工厂模式,抽象产品模式,具体产品模式。不再是由一个工厂类去实例化具体的产品,而是由抽象工厂的子类去实例化产品。简单来说,创建了一个抽象的工厂类,然后用其子类去获取具体的实例引用(这个子类只能获取单个实例)。
3.2.2、图示
3.2.3、代码实现
// 抽象产品角色
public interface Moveable {
void run();
}
// 具体产品角色
public class Plane implements Moveable {
@Override
public void run() {
System.out.println("plane....");
}
}
public class Broom implements Moveable {
@Override
public void run() {
System.out.println("broom.....");
}
}
// 抽象工厂
public abstract class VehicleFactory {
abstract Moveable create();
}
// 具体工厂
public class PlaneFactory extends VehicleFactory {
public Moveable create() {
return new Plane();
}
}
// 具体工厂
public class BroomFactory extends VehicleFactory {
public Moveable create() {
return new Broom();
}
}
// 测试类
public class Test {
public static void main(String[] args) {
VehicleFactory factory = new BroomFactory();
Moveable m = factory.create();
m.run();
}
}
3.2.4、注意
- 工厂方法模式有一个明显的优点就是耦合性较低,增加产品时,并不会影响原来的代码。但是代码较为复杂,代码量较大,每增加一个产品,都需要额外增加工厂类。
- 工厂方法模式中,工厂类和产品类往往可以依次对应。即一个抽象工厂对应一个抽象产品,一个具体工厂对应一个具体产品,这个具体的工厂就负责生产对应的产品。
3.3、抽象工厂模式
3.3.1、说明
围绕一个超级工厂,创建其他工厂。它有多个抽象产品类,每个抽象产品类可以派生出多个具体产品类,一个抽象工厂类,可以派生出多个具体工厂类,每个具体工厂类可以创建多个具体产品类的实例。
3.3.2、图示
3.3.3、代码实现
// 抽象手机
public interface Phone {
void start();
void shutdown();
}
// 抽象路由器
public interface Router {
void openWifi();
void shutDownWifi();
}
// 具体产品,小米手机
public class XiaomiPhone implements Phone {
@Override
public void start() {
System.out.println("小米开机");
}
@Override
public void shutdown() {
System.out.println("小米关机");
}
}
// 华为手机
public class HuaweiPhone implements Phone {
@Override
public void start() {
System.out.println("华为开机");
}
@Override
public void shutdown() {
System.out.println("华为关机");
}
}
// 小米路由器
public class XiaomiRouter implements Router {
@Override
public void openWifi() {
System.out.println("小米wifi开启");
}
@Override
public void shutDownWifi() {
System.out.println("小米wifi关闭");
}
}
// 华为路由器
public class HuaweiRouter implements Router {
@Override
public void openWifi() {
System.out.println("华为wifi开启");
}
@Override
public void shutDownWifi() {
System.out.println("华为wifi关闭");
}
}
// 抽象工厂
public interface ProductsFactory {
Phone phoneProduct();
Router routerProduct();
}
// 小米工厂
public class XiaomiFactory implements ProductsFactory {
@Override
public Phone phoneProduct() {
return new XiaomiPhone();
}
@Override
public Router routerProduct() {
return new XiaomiRouter();
}
}
// 华为工厂
public class HuaweiFactory implements ProductsFactory {
@Override
public Phone phoneProduct() {
return new HuaweiPhone();
}
@Override
public Router routerProduct() {
return new HuaweiRouter();
}
}
// 测试类
public class Client {
public static void main(String[] args) {
HuaweiFactory factory = new HuaweiFactory();
Phone phone = factory.phoneProduct();
phone.start();
}
}
四、代理模式
4.1、说明
在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。中介不仅可以为你找房子,还能收你钱。
代理模式一般涉及到的角色有:
-
抽象角色
声明真实对象和代理对象的【共同接口】,一般是接口或者是抽象类。
共同接口:租房子。中介和房东都可以完成租房子的事情。 -
代理角色
代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
相当于中介。
-
真实角色
代理角色所代表的真实对象,是我们最终引用的对象
相当于房东。
# 总结:
中介其实是没有房子的,真正的房子在房东手里,也就是说中介【代理角色】内部需要有对房东【真实角色】的一个信息的登记。那么我就可以操作房东的房子。
4.2、静态代理
4.2.1、代码实现
-
抽象角色
abstract class Subject{ public abstract void request(); }
-
真实角色
class RealSubject extends Subject{ public void request(){ System.out.println("from real subject"); } }
-
代理角色
class ProxySubject extends Subject{ private RealSubject subject = new RealSubject();// 代理角色内部引用了真实角色 public void request(){ // 在真实角色操作之前所附加的操作。 this.preRequest(); // 相当于中介帮你租房子 subject.request(); // 真实角色完成的事情 } private void preRequest(){ System.out.println("收你100块钱"); } }
-
测试
public class Client{ public static void main(String[] args){ Subject subject = new ProxySubject(); subject.request(); } }
4.2.2、分析
由以上代码可以看出,客户实际需要调用的是RealSubject类的租房子方法,现在用ProxySubject,来代理 RealSubject类,同样达到目的,同时还封装了其他方法(preRequest()),可以处理一些其他问题。
另外, 如果要按照上述的方法使用代理模式,那么真实角色必须是事先已经存在的,并将其作为代理对象的内部属性。但是实际使用时,一个真实角色必须对应一个代理角色,如果大量使用会导致类的急剧膨胀;此外,如果事先并不知道真实角色,该如何使用代理呢?这个问题可以通过Java的动态代理类来解决。
4.2.3、总结
静态代理有一个缺陷?就是说随着我们的业务系统或者说随着我们的需要被代理的类越来越多,那么我们就必须要提供一个一对一的服务,那么这样系统就会很臃肿.我们换一个思考方式:能不能让一个类来代理所有的类【能不能让一个类来去代理所有的真实的类】。
4.3、动态代理
4.3.1、说明
由于这个代理类需要代理的事物有很多,我们就不能针对具体的真实对象做代理了,但是我们仍然需要这个代理类,但是在程序里又写不出来,这个任务只能交给java来做了,由java自己来去生成一个代理类。但是有个问题?你想让人家java帮你生成人家就能帮你生成吗?那么你就必须要遵循java生成代理类一个约定了。
4.3.2、详解
Java动态代理类位于java.lang.reflect包下,一般主要涉及到以下两个类:
-
Interface InvocationHandler
该接口中仅定义了一个方法。
public object invoke(Object obj,Method method,Object[] args)
在实际使用时, 第一个参数obj一般是指代理类,method是被代理的方法,如上例中的request(),args为该方法的参数数组。 这个抽象方法在代理类中动态实现。
-
Proxy
这个类提供了很多静态方法用来去创建动态代理类和动态代理类的实例,并且呢,通过这个Proxy这个类里面的静态方法所创建的动态代理类都是这个Proxy的子类。
该类即为动态代理类,作用类似于上例中的ProxySubject,其中主要包含以下内容:
-
protected Proxy(InvocationHandler h):构造函数,用于给内部的h赋值。
-
static Object newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)
返回代理类的一个实例,返回后的代理类可以当作被代理类使用(可使用被代理类的在Subject接口中声明过的方法)。
-
4.3.3、动态代理类
所谓Dynamic Proxy是这样一种class:它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些interface。你当然可以把该class的实例当作这些interface中的任何一个来用。当然,这个Dynamic Proxy其实就是一个Proxy,它不会替你作实质性的工作, 在生成它的实例时你必须提供一个handler,由它接管实际的工作。
4.3.4、动态代理编写步骤
- 在使用动态代理类时,我们必须实现InvocationHandler接口.
- 创建一个实现接口InvocationHandler的类,它必须实现invoke方法
- 创建被代理的类以及接口
被代理的类:房东
接口:就是我们的Subject,也就是中介和房东之间达成的共识或者是契约/约定 - 通过Proxy的静态方法
newProxyInstance(ClassLoader loader,Class[] interfaces, InvocationHandlerh) 创建一个代理 - 通过代理调用方法
4.3.5、总结
- 每一个代理实例都会有一个处理器Handler;
- 当一个代理实例上的一个方法被调用的时候,那么就会去处理与之关联的处理器上的invoke方法。
4.3.6、代码实现
-
抽象角色
abstract class Subject{ public abstract void request(); }
-
真实角色
class RealSubject extends Subject{ public void request(){ System.out.println("from real subject"); } }
-
代理角色与之关联的处理器
public class ProxyHandler implements InvocationHandler{ // 动态代理类要维护一个真实对象的引用,它可以代理任意的对象 private Object target; public ProxyHandler(Object target){ this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{ System.out.println("我真正干活钱,要收你中介费了"); method.invoke(object, args); System.out.println("合作愉快,结束!"); return null; } }
-
客户端测试
public class Client{ public static void main(String[] args){ RealSubject real = new RealSubject(); InvocationHandler handler = new ProxyHandler(real); Class<?> clazz = handler.getClass(); // 运行的时候动态生成的一个class,然后生成class的对象。 Subject proxyObj = (Subject)Proxy.newProxyInstance(classType.getClassLoader(), realSubject.getClass().getInterface(), handler); // 调用代理对象身上的request方法的时候,流程就会里面跳转到Handler中的invoke方法就会被调用。 proxyObj.request(); } }