设计模式的分类
总体来说设计模式分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
设计模式
- 单例模式
- 工厂模式
- 观察者模式
- 适配器模式
- 模仿方法模式
- 策略模式
- 责任链模式
- 装饰者模式
面向对象的基本设计原则
单例模式
懒汉模式+三种线程安全方式:1.synchronized 2.双重检测-问题+拓展volatile 3.静态内部类;
饿汉模式;
比较;
单例模式有以下特点:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
一、懒汉式单例
构造方法+null+getInstance(判断);
//懒汉式单例类.在第一次调用的时候实例化自己 public class Singleton { private Singleton() {} private static Singleton single=null; //静态工厂方法 public static Singleton getInstance() { if (single == null) { single = new Singleton(); } return single; } }
实现线程安全:
1、在getInstance方法上加同步
public static synchronized Singleton getInstance() { if (single == null) { single = new Singleton(); } return single; }
2、双重检查锁定
(同步类+双重判断)
public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; }
(为什么要第二次检查?比如B线程运行到第一次判断是否为空的时候,与此同时A线程已经进入了同步块。无论是否做第二次判断,接下来A线程肯定能new一个对象。如果不做第二次判断的话,之后A线程退出同步块,B线程进入同步块之后,B线程还是会new一个对象)(双重检查锁定问题-创建实例的操作非原子化,在JVM中是无序写入,所以可能出现一个线程初始化一半的情况--即还没有获得实例,但instance已经不为空并且线程2获得锁了。)(参考:双重检测)
3、静态内部类
(LazyHolder + 构造方法 + getInstance)
1 public class Singleton { 2 private static class LazyHolder { 3 private static final Singleton INSTANCE = new Singleton(); 4 } 5 private Singleton (){} 6 public static final Singleton getInstance() { 7 return LazyHolder.INSTANCE; 8 } 9 }
二、饿汉式单例
构造方法+实例+getInstance;
//饿汉式单例类.在类初始化时,已经自行实例化 public class Singleton1 { private Singleton1() {} private static final Singleton1 single = new Singleton1(); //静态工厂方法 public static Singleton1 getInstance() { return single; } }
饿汉式和懒汉式区别
从名字上来说,饿汉和懒汉,
饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,
而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。
另外从以下两点再区分以下这两种方式:
1、线程安全:
饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,
懒汉式本身是非线程安全的,为了实现线程安全有几种写法,分别是上面的1、2、3,这三种实现在资源加载和性能方面有些区别。
2、资源加载和性能:
饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,
而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。
至于1、2、3这三种实现又有些区别,
第1种,在方法调用上加了同步,虽然线程安全了,但是每次都要同步,会影响性能,毕竟99%的情况下是不需要同步的,
第2种,在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗
第3种,利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以一般我倾向于使用这一种。
拓展-volatile
在当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。
要解决这个问题,只需要像在本程序中的这样,把该变量声明为volatile(不稳定的)即可,这就指示JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。一般说来,多任务环境下各任务间共享的标志都应该加volatile修饰。
Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。 参考:volatile
拓展-Runtime类
Runtime类在java中的设计就是按照单例模式之饿汉式设计的
class Runtime { private Runtime() {} private static Runtime currentRuntime = new Runtime(); public static Runtime getRuntime() { return currentRuntime; } }
- 每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。可以通过 getRuntime 方法获取当前运行时。
- 应用程序不能创建自己的 Runtime 类实例。 参考:常用设计模式
参考:单例模式
工厂方法模式
工厂模式可以分为三类:
1)简单工厂模式(Simple Factory)
2)工厂方法模式(Factory Method)
3)抽象工厂模式(Abstract Factory)
这三种模式从上到下逐步抽象,并且更具一般性。
GOF在《设计模式》一书中将工厂模式分为两类:工厂方法模式(Factory Method)与抽象工厂模式(Abstract Factory)。
将简单工厂模式(Simple Factory)看为工厂方法模式的一种特例,两者归为一类。
一、简单工厂模式
又叫静态工厂方法模式,它定义一个具体的工厂类负责创建一些类的实例
/*产品类*/ abstract class BMW { public BMW(){ } } public class BMW320 extends BMW { public BMW320() { System.out.println("制造-->BMW320"); } } public class BMW523 extends BMW{ public BMW523(){ System.out.println("制造-->BMW523"); } } /*工厂类*/ public class Factory { public BMW createBMW(int type) { switch (type) { case 320: return new BMW320(); case 523: return new BMW523(); default: break; } return null; } } /*客户类*/ public class Customer { public static void main(String[] args) { Factory factory = new Factory(); BMW bmw320 = factory.createBMW(320); BMW bmw523 = factory.createBMW(523); } }
1 /* * 抽象的动物类,里面有抽象的方法 */ 2 public abstract class Animal { 3 public abstract void eat(); 4 } 5 /* * 具体的动物猫继承抽象动物类,重写抽象方法 */ 6 public class Cat extends Animal { 7 @Override 8 public void eat() { 9 System.out.println("猫吃鱼"); 10 } 11 } 12 /* * 具体的动物狗继承抽象动物类,重写抽象方法 */ 13 public class Dog extends Animal { 14 @Override 15 public void eat() { 16 System.out.println("狗吃肉"); 17 } 18 } 19 /* * 动物工厂类,可以造猫和狗 */ 20 public class AnimalFactory { 21 private AnimalFactory() { } 22 public static Animal createAnimal(String type) { 23 if ("dog".equals(type)) { 24 return new Dog(); 25 } else if ("cat".equals(type)) { 26 return new Cat(); 27 } else { 28 return null; 29 } 30 } 31 } 32 /* * 测试类 */ 33 public class AnimalDemo { 34 public static void main(String[] args) { 35 // 工厂有了后,通过工厂给造 36 Animal a = AnimalFactory.createAnimal("dog"); 37 a.eat(); 38 a = AnimalFactory.createAnimal("cat"); 39 a.eat(); 40 // NullPointerException 41 a = AnimalFactory.createAnimal("pig"); 42 if (a != null) { 43 a.eat(); 44 } else { 45 System.out.println("对不起,暂时不提供这种动物"); 46 } 47 } 48 }
运用了简单工厂模式后,就不用每次用的时候去new对象,而是直接去调用这个工厂类里面的具体方法,它会给我们返回一个已经new好的对象。
- 优点:客户端不需要在负责对象的创建,从而明确了各个类的职责
-
缺点:这个静态工厂类负责所有对象的创建,如果有新的对象增加,或者某些对象的创建方式不同,就需要不断的修改工厂类,不利于后期的维护
二、工厂方法模式
工厂方法模式去掉了简单工厂模式中工厂方法的静态属性,使得它可以被子类继承。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担。 工厂方法模式组成:
- 一个抽象工厂、多个具体工厂;
- 一个抽象产品、多个具体产品;
- 每个具体工厂创建一个具体产品;
1)抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。
2)具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。
3)抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。
4)具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现。
/*产品类*/ abstract class BMW { public BMW(){ } } public class BMW320 extends BMW { public BMW320() { System.out.println("制造-->BMW320"); } } public class BMW523 extends BMW{ public BMW523(){ System.out.println("制造-->BMW523"); } } /*工厂类*/ interface FactoryBMW { BMW createBMW(); } public class FactoryBMW320 implements FactoryBMW{ @Override public BMW320 createBMW() { return new BMW320(); } } public class FactoryBMW523 implements FactoryBMW { @Override public BMW523 createBMW() { return new BMW523(); } } /*客户类*/ public class Customer { public static void main(String[] args) { FactoryBMW320 factoryBMW320 = new FactoryBMW320(); BMW320 bmw320 = factoryBMW320.createBMW(); FactoryBMW523 factoryBMW523 = new FactoryBMW523(); BMW523 bmw523 = factoryBMW523.createBMW(); } }
/* * 抽象的动物类,里面有抽象的方法 */ public abstract class Animal { public abstract void eat(); } /* * 工厂类接口,里面有抽象的创造动物的方法 */ public interface Factory { public abstract Animal createAnimal(); } /* * 具体的猫类继承抽象动物类,重写抽象方法 */ public class Cat extends Animal { @Override public void eat() { System.out.println("猫吃鱼"); } } /* * 猫工厂类实现工厂类并实现它的抽象方法,返回一个猫对象 */ public class CatFactory implements Factory { @Override public Animal createAnimal() { return new Cat(); } } /* * 具体的狗类继承抽象动物类,重写抽象方法 */ public class Dog extends Animal { @Override public void eat() { System.out.println("狗吃肉"); } } /* * 狗工厂类实现工厂类并实现它的抽象方法,返回一个狗对象 */ public class DogFactory implements Factory { @Override public Animal createAnimal() { return new Dog(); } } /* * 测试类 */ public class AnimalDemo { public static void main(String[] args) { // 需求:我要买只狗 Factory f = new DogFactory(); Animal a = f.createAnimal(); a.eat(); //需求:我要买只猫 f = new CatFactory(); a = f.createAnimal(); a.eat(); } }
- 优点:客户端不需要在负责对象的创建,从而明确了各个类的职责,如果有新的对象增加,只需要增加一个具体的类和具体的工厂类即可,不影响已有的代码,后期维护容易,增强了系统的扩展性
- 缺点:需要额外的编写代码,增加了工作量
工厂方法模式仿佛已经很完美的对对象的创建进行了包装,使得客户程序中仅仅处理抽象产品角色提供的接口,但使得对象的数量成倍增长。当产品种类非常多时,会出现大量的与之对应的工厂对象,这不是我们所希望的。
抽象工厂模式
工厂方法模式与抽象工厂模式区别:
工厂方法模式:
一个抽象产品类,可以派生出多个具体产品类。
一个抽象工厂类,可以派生出多个具体工厂类。
每个具体工厂类只能创建一个具体产品类的实例。
抽象工厂模式:
多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。
一个抽象工厂类,可以派生出多个具体工厂类。
每个具体工厂类可以创建多个具体产品类的实例。
区别:
工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个。
工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。
- 一个抽象工厂、多个具体工厂;
- 多个抽象产品、每个派生多个具体产品;
- 每个工厂可以创建多个具体产品;
- 与工厂方法模式区别:可以多个抽象产品类;每个具体工厂可以创建多个具体产品实例;
产品类:
//发动机以及型号 abstract class Engine { } public class EngineA extends Engine{ public EngineA(){ System.out.println("制造-->EngineA"); } } public class EngineB extends Engine{ public EngineB(){ System.out.println("制造-->EngineB"); } } //空调以及型号 abstract class Aircondition { } public class AirconditionA extends Aircondition{ public AirconditionA(){ System.out.println("制造-->AirconditionA"); } } public class AirconditionB extends Aircondition{ public AirconditionB(){ System.out.println("制造-->AirconditionB"); } }
工厂类:
1 //创建工厂的接口 2 public interface AbstractFactory { 3 //制造发动机 4 public Engine createEngine(); 5 //制造空调 6 public Aircondition createAircondition(); 7 } 8 9 10 //为宝马320系列生产配件 11 public class FactoryBMW320 implements AbstractFactory{ 12 13 @Override 14 public Engine createEngine() { 15 return new EngineA(); 16 } 17 @Override 18 public Aircondition createAircondition() { 19 return new AirconditionA(); 20 } 21 } 22 //宝马523系列 23 public class FactoryBMW523 implements AbstractFactory { 24 25 @Override 26 public Engine createEngine() { 27 return new EngineB(); 28 } 29 @Override 30 public Aircondition createAircondition() { 31 return new AirconditionB(); 32 } 33 34 35 }
客户类:
public class Customer { public static void main(String[] args){ //生产宝马320系列配件 FactoryBMW320 factoryBMW320 = new FactoryBMW320(); factoryBMW320.createEngine(); factoryBMW320.createAircondition(); //生产宝马523系列配件 FactoryBMW523 factoryBMW523 = new FactoryBMW523(); factoryBMW523.createEngine(); factoryBMW523.createAircondition(); } }
参考:抽象工厂模式
观察者模式
1)简介
- 抽象被观察者(attach、detach、notify)、具体被观察者;
- 抽象观察者(update)、具体观察者;
- 应用场景:一个对象状态更新,其他对象同步更新;仅需通知其他对象更新而不需要知道其他对象细节;优点:耦合双方都依赖于抽象,各种变换不会影响。
定义
观察者模式(又被称为发布-订阅(Publish/Subscribe)模式,属于行为型模式的一种,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。
观察者模式结构图
- Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
- ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
- Observer:抽象观察者,是观察者者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
- ConcrereObserver:具体观察者,是实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。
2)观察者模式简单实现
观察者模式这种发布-订阅的形式我们可以拿微信公众号来举例,假设微信用户就是观察者,微信公众号是被观察者,有多个的微信用户关注了程序猿这个公众号,当这个公众号更新时就会通知这些订阅的微信用户。好了我们来看看用代码如何实现:
抽象观察者(Observer)
里面定义了一个更新的方法:
public interface Observer { public void update(String message); }
具体观察者(ConcrereObserver)
微信用户是观察者,里面实现了更新的方法:
public class WeixinUser implements Observer { // 微信用户名 private String name; public WeixinUser(String name) { this.name = name; } @Override public void update(String message) { System.out.println(name + "-" + message); } }
抽象被观察者(Subject)
抽象主题,提供了attach、detach、notify三个方法:
public interface Subject { /** * 增加订阅者 * @param observer */ public void attach(Observer observer); /** * 删除订阅者 * @param observer */ public void detach(Observer observer); /** * 通知订阅者更新消息 */ public void notify(String message); }
具体被观察者(ConcreteSubject)
微信公众号是具体主题(具体被观察者),里面存储了订阅该公众号的微信用户,并实现了抽象主题中的方法:
public class SubscriptionSubject implements Subject { //储存订阅公众号的微信用户 private List<Observer> weixinUserlist = new ArrayList<Observer>(); @Override public void attach(Observer observer) { weixinUserlist.add(observer); } @Override public void detach(Observer observer) { weixinUserlist.remove(observer); } @Override public void notify(String message) { for (Observer observer : weixinUserlist) { observer.update(message); } } }
客户端调用
public class Client { public static void main(String[] args) { SubscriptionSubject mSubscriptionSubject=new SubscriptionSubject(); //创建微信用户 WeixinUser user1=new WeixinUser("杨影枫"); WeixinUser user2=new WeixinUser("月眉儿"); WeixinUser user3=new WeixinUser("紫轩"); //订阅公众号 mSubscriptionSubject.attach(user1); mSubscriptionSubject.attach(user2); mSubscriptionSubject.attach(user3); //公众号更新发出消息给订阅的微信用户 mSubscriptionSubject.notify("刘望舒的专栏更新了"); } }
3)观察者模式使用场景
观察者模式的应用场景:
1、 对一个对象状态的更新,需要其他对象同步更新,而且其他对象的数量动态可变。
2、 对象仅需要将自己的更新通知给其他对象而不需要知道其他对象的细节。
优点
解除耦合,让耦合的双方都依赖于抽象,从而使得各自的变换都不会影响另一边的变换。
参考:观察者模式
代理模式
1)简介
代理模式也叫委托模式,是结构型设计模式的一种。在现实生活中我们用到类似代理模式的场景有很多,比如代购、代理上网、打官司等。
定义
为其他对象提供一种代理以控制这个对象的访问。
代理模式结构图
- Subject:抽象主题类,声明真实主题与代理的共同接口方法。
- RealSubject:真实主题类,定义了代理所表示的真实对象,客户端通过代理类间接的调用真实主题类的方法。
- ProxySubject:代理类,持有对真实主题类的引用,在其所实现的接口方法中调用真实主题类中相应的接口方法执行。
- Client:客户端类。
2)代理模式的简单实现
抽象主题类(Subject)
抽象主题类具有真实主题类和代理的共同接口方法,我想要代购,那共同的方法就是购买:
public interface IShop { //购买 void buy(); }
真实主题类(RealSubject)
购买者 实现了IShop接口提供的 buy()方法:
public class Buyer implements IShop { @Override public void buy() { System.out.println("购买"); } }
代理类(ProxySubject)
我找的代理类同样也要实现IShop接口,并且要持有被代理者,在buy()方法中调用了被代理者的buy()方法:
public class Purchasing implements IShop { private IShop mShop; public Purchasing(IShop shop){ mShop=shop; } @Override public void buy() { mShop.buy(); } }
客户端类(Client)
public class Client { public static void main(String[] args){ //创建Buyer IShop buyer=new Buyer(); //创建代购者并将buyer作为构造函数传 IShop purchasing=new Purchasing(buyer); purchasing.buy(); } }
代理类包含了真实主题类(被代理者),最终调用的都是真实主题类(被代理者)实现的方法。
3)动态代理的简单实现
从编码的角度来说,代理模式分为静态代理和动态代理,上面的例子是静态代理,在代码运行前就已经存在了代理类的class编译文件,而动态代理则是在代码运行时通过反射来动态的生成代理类的对象,并确定到底来代理谁。也就是我们在编码阶段不需要知道代理谁,代理谁我们将会在代码运行时决定。Java提供了动态的代理接口InvocationHandler,实现该接口需要重写invoke()方法。下面我们在上面静态代理的例子上做修改:
创建动态代理类:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class DynamicPurchasing implements InvocationHandler{ private Object obj; public DynamicPurchasing(Object obj){ this.obj=obj; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result=method.invoke(obj, args); return result; } }
在动态代理类中我们声明一个Object的引用,该引用指向被代理类,我们调用被代理类的具体方法在invoke()方法中执行。接下来我们修改客户端类代码:
import java.lang.reflect.Proxy; public class Client { public static void main(String[] args){ //创建buyer IShop buyer=new buyer(); //创建动态代理 DynamicPurchasing mDynamicPurchasing=new DynamicPurchasing(buyer); //创建buyer的ClassLoader ClassLoader loader=buyer.getClass().getClassLoader(); //动态创建代理类 IShop purchasing= (IShop) Proxy.newProxyInstance(loader,new Class[]{IShop.class},mDynamicPurchasing); purchasing.buy(); } }
4)代理模式的应用
代理模式类型
代理模式的类型主要有以下几点:
- 远程代理:为一个对象在不同的地址空间提供局部代表,这样系统可以将Server部分的事项隐藏。
- 虚拟代理:使用一个代理对象表示一个十分耗资源的对象并在真正需要时才创建。
- 安全代理:用来控制真实对象访问时的权限。
- 智能指引:当调用真实的对象时,代理处理另外一些事,比如计算真实对象的引用计数,当该对象没有引用时,可以自动释放它;或者访问一个实际对象时,检查是否已经能够锁定它,以确保其他对象不能改变它。
代理模式使用场景
无法或者不想直接访问某个对象时可以通过一个代理对象来间接的访问。
参考:代理模式