设计模式
2016-07-29 11:40
309 查看
1单例模式
2简单工厂 模式
3策略模式
4工厂方法模式
5抽象方法模式
6装饰模式
7代理模式
8观察者模式
9适配器模式
(1):单一职责原则
定义: 一个类只负责一项职责。
问题由来:类T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。
(2):里氏替换原则
任何基类可以出现的地方,子类一定可以出现
只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。
类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。
(3):依赖倒置原则
程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
问题由来:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。
解决方案:将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。
(4):接口隔离原则
客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
(5):迪米特法则
定义:一个对象应该对其他对象保持最少的了解。如果两个类平时很少交互,一旦交互最好以来第三方接口,把自己的内部封装成private,这样降低二者之间的耦合。
问题由来:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。
(6):开闭原则
一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。
改进:双重检查锁定
第一重判断:改进了不用每次都同步的缺点
第二重判断:如果两个线程都进入了第一重判断,由于同步,只能进来一个。A进入了创建了实例,出来后B还可以进入,如果没有第二次判断,就会生成多个instance
另外可能失效:java的指令重排序。single = new Singleton();可以分成多条汇编指令
(1)、给Singleton实例分配内存(2)、调用构造函数,初始化成员(3)、将instance对象指向分配内存的空间,注意此时instance就不为null了
可以是123,也可以是132,如果是132,A执行3的时候,2未执行,此时instance就不为空,B就把instance取走了,这就是失效的原因。
解决办法:private volatile static Singleton single=null;
静态内部类方法:推荐使用
第一次加载Singleton 时不会SingletonHolder 初始化,只有调用getInstance方法虚拟机才会初始化SingletonHolder ,这样既可以保证线程安全,也能保证对象的唯一性。
二、饿汉单例模式
饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,
而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。
客户端调用:Context context;
特点分析:
1、策略模式是一种定义一系列算法的方法,所有算法实现的是相同的工作,只是实现不同,它可以以相同的方式调用,减少了算法类与使用算法类的耦合
2、简化了单元测试,每一个算法类可以通过对自己的接口单独测试
3、避免了不同行为堆砌在一个类中,避免了使用if语句
4、将不同的算法继承Strategy接口,分别实现不同的算法,然后传递给Context类,Context类封装了抽象Strategy,用Context类方法调用Strategy的方法。
特点:
1、与简单工厂相比,克服了开放封闭原则,比如我要添加一个类,求M的N次方,简单工厂方法需要添加一个case的分支条件,这就等于是对外修改了
工厂方法模式是定义一个创建对象的接口,让子类决定实例化哪一个类,把一个类的实例化延迟到子类去了。这样我要添加一个类,求M的N次方,就不需要更改原有工厂类了,只需要增加相应的工厂类和运算类即可。
2、工厂方法模式实现时,把决定实例化哪一个工厂的运算类交由客户端,原来修改的是原工厂,现在修改的是客户端。
但是实际上,由于多态和继承,客户端秩序修改一个地方就可以了。
Ifactory operFactory = new AddFactory();
3、简单工厂方法最大优点是工厂中包含了必要的逻辑判断,根据客户端的选择动态的实例化类的对象,对于客户端来说去除了与产品的依赖。
工厂方法模式继承了简单工厂方法的优点,把工厂类进一步抽象,克服了他的修改缺点
新增加部门表:
特点分析:
1、在一个应用中只需要初始化一次,就可以使用使用该产品的不同配置(user表和不部门表),这样更改少,如果要更换数据库,只需要重新实例化对象给factory
2、客户端是通过他们的抽象接口操纵实例。
缺点:如果想要增加项目表Project,需要增加的地方:增加IProject,SqlserverProject,AccessProject,类似于增加部门表。还要修改:Ifactory,SqlserverFactory,AccessFactory,
修改的地方多,如果有100次就需要修改100次
3、改进措施:使用反射
常规方法是写明了实例化的对象,是事先编译好的代码。
在反射中是根据传递的字符串来动态的实例化对象。
Component 定义对象接口
ConcreteComponent :原有的对象,在此对象上添加装饰
Decorator :装饰Component 类,从外类扩展Component 类的功能
ConcreteDecoratorA 具体装饰角色
ConcreteDecoratorB 具体装饰角色
客户端:
使用场景:
当系统需要新的功能时,是向旧的类中添加新的代码,这些新加的代码通常装饰了原有类的主要行为,添加新的字段或行为,而新加的这些只有在某种特性情况下才执行的特殊行为。这样客户端就可以在原有的类上有选择的添加各种行为,就可以有选择的使用各个使用装饰功能的对象了
优点
(1)装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。装饰模式允许系统动态决定“贴上”一个需要的“装饰”,或者除掉一个不需要的“装饰”。继承关系则不同,继承关系是静态的,它在系统运行前就决定了。
(2)通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。
(3)把类中的装饰功能从类中去掉,简化原有的类,即把核心功能与装饰功能区分开了
缺点
可以比使用继承关系需要较少数目的类。使用较少的类,当然使设计比较易于进行。但是,在另一方面,使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相像。
抽象对象角色
真实对象角色
代理对象角色
客户端
优点:
1)代理模式能将代理对象与真正被调用的对象分离,在一定程度上降低了系统的耦合度。
2)代理模式在客户端和目标对象之间起到一个中介作用,这样可以起到保护目标对象的作用。代理对象也可以对目标对象调用之前进行其他操作。
缺点:
1)在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢。
2)增加了系统的复杂度。
使用场景:
1)远程代理,也就是为一个对象在不同的地址空间提供局部代表。这样可以隐藏一个对象存在于不同地址空间的事实。
2)虚拟代理,根据需要创建开销很大的对象。通过它来存放实例化需要很长时间的对象。比如打开很大的html页面,可以立即看到文字,但是图片是一张一张下载的,此时代理存储了真实图片的路径和尺寸
3)安全代理,用来控制真实对象访问时的权限。
4)智能指引,当调用目标对象时,代理可以处理其他的一些操作。
举例说明:
做一个平台,为了接入非常多的第三方系统,那平台必然需要提供接口。但问题来了,第三方系统调用平台的接口不一而足,那么如果做一个统一的webservice面向所有的第三方系统,实际上也是可以的,但是安全性必然不好。常用的做法是将平台的接口大致分为几种,然后编写几个代理服务,它们分别提供不同的接口服务,并对不同的第三方厂家进行开放,这样在保证整套系统正常运行的情况下还能在一定程度上提高其安全性。
定义了一种一对多的依赖关系,让多个观察者对象同时监听某一主题对象,这个主题对象在状态发生改变的时候,会通知所有观察者,并使它们能够自动更新自己
客户端类
优点
观察者模式解除了主题和具体观察者的耦合,让耦合的双方都依赖于抽象,而不是依赖具体。从而使得各自的变化都不会影响另一边的变化。
缺点
依赖关系并未完全解除,抽象通知者依旧依赖抽象的观察者。
适用场景
当一个对象的改变需要给变其它对象时,而且它不知道具体有多少个对象有待改变时。
一个抽象模型有两个方面,当其中一个方面依赖于另一个方面,这时用观察者模式可以将这两者封装在独立的对象中使它们各自独立地改变和复用。
客户端:
优点:
两个类所做的事情相同或相似时,但是具有不同的接口使用它们,这样可以统一接口
缺点:
能预防接口不同的问题,及时重构,问题不至于扩大,只有碰到无法改变原有设计和代码的情况时,才考虑适配。最好能在事前控制好
2简单工厂 模式
3策略模式
4工厂方法模式
5抽象方法模式
6装饰模式
7代理模式
8观察者模式
9适配器模式
(1):单一职责原则
定义: 一个类只负责一项职责。
问题由来:类T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。
(2):里氏替换原则
任何基类可以出现的地方,子类一定可以出现
只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。
类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。
(3):依赖倒置原则
程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
问题由来:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。
解决方案:将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。
(4):接口隔离原则
客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
(5):迪米特法则
定义:一个对象应该对其他对象保持最少的了解。如果两个类平时很少交互,一旦交互最好以来第三方接口,把自己的内部封装成private,这样降低二者之间的耦合。
问题由来:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。
(6):开闭原则
一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。
1、单例模式
一、懒汉模式:public class Singleton { private Singleton() {} private static Singleton single=null; //缺点:每一次都要 synchronized 同步,造成不必要的开销 public static synchronized Singleton getInstance() { if (single == null) { single = new Singleton(); } return single; } }
改进:双重检查锁定
第一重判断:改进了不用每次都同步的缺点
第二重判断:如果两个线程都进入了第一重判断,由于同步,只能进来一个。A进入了创建了实例,出来后B还可以进入,如果没有第二次判断,就会生成多个instance
另外可能失效:java的指令重排序。single = new Singleton();可以分成多条汇编指令
(1)、给Singleton实例分配内存(2)、调用构造函数,初始化成员(3)、将instance对象指向分配内存的空间,注意此时instance就不为null了
可以是123,也可以是132,如果是132,A执行3的时候,2未执行,此时instance就不为空,B就把instance取走了,这就是失效的原因。
解决办法:private volatile static Singleton single=null;
public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; }
静态内部类方法:推荐使用
第一次加载Singleton 时不会SingletonHolder 初始化,只有调用getInstance方法虚拟机才会初始化SingletonHolder ,这样既可以保证线程安全,也能保证对象的唯一性。
public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } }
二、饿汉单例模式
//饿汉式单例类.在类初始化时,已经自行实例化,以后不再改变,所以天生是线程安全的 public class Singleton1 { private Singleton1() {} private static final Singleton1 single = new Singleton1(); //静态工厂方法 public static Singleton1 getInstance() { return single; } }
饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,
而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。
2、简单工厂 模式
3、策略模式
客户端调用:Context context;
特点分析:
1、策略模式是一种定义一系列算法的方法,所有算法实现的是相同的工作,只是实现不同,它可以以相同的方式调用,减少了算法类与使用算法类的耦合
2、简化了单元测试,每一个算法类可以通过对自己的接口单独测试
3、避免了不同行为堆砌在一个类中,避免了使用if语句
4、将不同的算法继承Strategy接口,分别实现不同的算法,然后传递给Context类,Context类封装了抽象Strategy,用Context类方法调用Strategy的方法。
4、工厂方法模式
特点:
1、与简单工厂相比,克服了开放封闭原则,比如我要添加一个类,求M的N次方,简单工厂方法需要添加一个case的分支条件,这就等于是对外修改了
工厂方法模式是定义一个创建对象的接口,让子类决定实例化哪一个类,把一个类的实例化延迟到子类去了。这样我要添加一个类,求M的N次方,就不需要更改原有工厂类了,只需要增加相应的工厂类和运算类即可。
2、工厂方法模式实现时,把决定实例化哪一个工厂的运算类交由客户端,原来修改的是原工厂,现在修改的是客户端。
但是实际上,由于多态和继承,客户端秩序修改一个地方就可以了。
Ifactory operFactory = new AddFactory();
3、简单工厂方法最大优点是工厂中包含了必要的逻辑判断,根据客户端的选择动态的实例化类的对象,对于客户端来说去除了与产品的依赖。
工厂方法模式继承了简单工厂方法的优点,把工厂类进一步抽象,克服了他的修改缺点
5、抽象方法模式
抽象方法模式与工厂方法的区别就是一个工厂可以创建多个产品,比如:不同的数据库不同的表,用户表IUser和部门表IDepartment新增加部门表:
特点分析:
1、在一个应用中只需要初始化一次,就可以使用使用该产品的不同配置(user表和不部门表),这样更改少,如果要更换数据库,只需要重新实例化对象给factory
2、客户端是通过他们的抽象接口操纵实例。
缺点:如果想要增加项目表Project,需要增加的地方:增加IProject,SqlserverProject,AccessProject,类似于增加部门表。还要修改:Ifactory,SqlserverFactory,AccessFactory,
修改的地方多,如果有100次就需要修改100次
3、改进措施:使用反射
常规方法是写明了实例化的对象,是事先编译好的代码。
在反射中是根据传递的字符串来动态的实例化对象。
6、装饰模式
Component 定义对象接口
public interface Component { public void sampleOperation(); }
ConcreteComponent :原有的对象,在此对象上添加装饰
public class ConcreteComponent implements Component { @Override public void sampleOperation() { // 具体对象的操作 } }
Decorator :装饰Component 类,从外类扩展Component 类的功能
public class Decorator implements Component{ private Component component; public Decorator(Component component){ this.component = component; } @Override public void sampleOperation() { component.sampleOperation();// 执行的是传递过来的 } }
ConcreteDecoratorA 具体装饰角色
public class ConcreteDecoratorA extends Decorator { public ConcreteDecoratorA(Component component) { super(component); } @Override public void sampleOperation() { super.sampleOperation();//运行原Component 的sampleOperation() // 先运行原对象的操作,再添加自己A的操作 } }
ConcreteDecoratorB 具体装饰角色
public class ConcreteDecoratorB extends Decorator { public ConcreteDecoratorB(Component component) { super(component); } @Override public void sampleOperation() { super.sampleOperation(); // 先运行原对象的操作,再添加自己B的操作 } }
客户端:
public class Client { public static void main(String[] args) { ConcreteComponent c = new ConcreteComponent(); ConcreteDecoratorA a = new ConcreteDecoratorA(c);//在c上加a的装饰 ConcreteDecoratorB b = new ConcreteDecoratorB(a);//在a上加b的装饰 b.sampleOperation(); //最终执行b的方法 } }
使用场景:
当系统需要新的功能时,是向旧的类中添加新的代码,这些新加的代码通常装饰了原有类的主要行为,添加新的字段或行为,而新加的这些只有在某种特性情况下才执行的特殊行为。这样客户端就可以在原有的类上有选择的添加各种行为,就可以有选择的使用各个使用装饰功能的对象了
优点
(1)装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。装饰模式允许系统动态决定“贴上”一个需要的“装饰”,或者除掉一个不需要的“装饰”。继承关系则不同,继承关系是静态的,它在系统运行前就决定了。
(2)通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。
(3)把类中的装饰功能从类中去掉,简化原有的类,即把核心功能与装饰功能区分开了
缺点
可以比使用继承关系需要较少数目的类。使用较少的类,当然使设计比较易于进行。但是,在另一方面,使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相像。
7、代理模式
抽象对象角色
public abstract class AbstractObject { public abstract void operation(); }
真实对象角色
public class RealObject extends AbstractObject { @Override public void operation() { //真实的一些操作 System.out.println("一些操作"); } }
代理对象角色
public class ProxyObject extends AbstractObject{ RealObject realObject = new RealObject();//包含真实请求对象 @Override public void operation() { //调用目标对象之前可以做相关操作 realObject.operation(); //调用目标对象之后可以做相关操作 } }
客户端
public class Client { public static void main(String[] args) { AbstractObject obj = new ProxyObject(); obj.operation(); } }
优点:
1)代理模式能将代理对象与真正被调用的对象分离,在一定程度上降低了系统的耦合度。
2)代理模式在客户端和目标对象之间起到一个中介作用,这样可以起到保护目标对象的作用。代理对象也可以对目标对象调用之前进行其他操作。
缺点:
1)在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢。
2)增加了系统的复杂度。
使用场景:
1)远程代理,也就是为一个对象在不同的地址空间提供局部代表。这样可以隐藏一个对象存在于不同地址空间的事实。
2)虚拟代理,根据需要创建开销很大的对象。通过它来存放实例化需要很长时间的对象。比如打开很大的html页面,可以立即看到文字,但是图片是一张一张下载的,此时代理存储了真实图片的路径和尺寸
3)安全代理,用来控制真实对象访问时的权限。
4)智能指引,当调用目标对象时,代理可以处理其他的一些操作。
举例说明:
做一个平台,为了接入非常多的第三方系统,那平台必然需要提供接口。但问题来了,第三方系统调用平台的接口不一而足,那么如果做一个统一的webservice面向所有的第三方系统,实际上也是可以的,但是安全性必然不好。常用的做法是将平台的接口大致分为几种,然后编写几个代理服务,它们分别提供不同的接口服务,并对不同的第三方厂家进行开放,这样在保证整套系统正常运行的情况下还能在一定程度上提高其安全性。
8、观察者模式
定义了一种一对多的依赖关系,让多个观察者对象同时监听某一主题对象,这个主题对象在状态发生改变的时候,会通知所有观察者,并使它们能够自动更新自己
//1、抽象主题角色类 public abstract class Subject { // 用来保存注册的观察者对象 private List<Observer> list = new ArrayList<Observer>(); // 注册观察者对象 public void attach(Observer observer){ list.add(observer); System.out.println("Attached an observer"); } // 删除观察者对象 public void detach(Observer observer){ list.remove(observer); } //通知所有注册的观察者对象 public void nodifyObservers(String newState){ for(Observer observer : list){ observer.update(newState); } } } //2、具体主题角色类 public class ConcreteSubject extends Subject{ private String state; public String getState() { return state; } public void change(String newState){ state = newState; System.out.println("主题状态为:" + state); //状态发生改变,通知各个观察者 this.nodifyObservers(state); } } //3、抽象观察者角色类 public interface Observer { // 更新的状态 public void update(String state); } //4、具体观察者角色类 public class ConcreteObserver implements Observer { //观察者的状态 private String observerState; @Override public void update(String state) { //更新观察者的状态,使其与目标的状态保持一致 observerState = state; System.out.println("状态为:"+observerState); } }
客户端类
public class Client { public static void main(String[] args) { //创建主题对象 ConcreteSubject subject = new ConcreteSubject(); //创建观察者对象 Observer observer = new ConcreteObserver(); //将观察者对象登记到主题对象上 subject.attach(observer); //改变主题对象的状态,会通知所有观察者改变 subject.change("new state"); } }
优点
观察者模式解除了主题和具体观察者的耦合,让耦合的双方都依赖于抽象,而不是依赖具体。从而使得各自的变化都不会影响另一边的变化。
缺点
依赖关系并未完全解除,抽象通知者依旧依赖抽象的观察者。
适用场景
当一个对象的改变需要给变其它对象时,而且它不知道具体有多少个对象有待改变时。
一个抽象模型有两个方面,当其中一个方面依赖于另一个方面,这时用观察者模式可以将这两者封装在独立的对象中使它们各自独立地改变和复用。
9、适配器模式
//1、客户期待的接口 public interface Target { //普通请求 public void sampleOperation(); } //2、Adaptee是一个需要适配的类 public class Adaptee { //特殊请求 public void specificOperation(){} } //3、适配器角色Adapter通过内部包装一个Adaptee对象,把需要适配的接口转换为目标接口 public class Adapter implements Target { private Adaptee adaptee = new Adaptee();//简历私有的Adaptee对象 @Override public void sampleOperation() { adaptee.specificOperation();//把sampleOperation方法实际上变为specificOperation方法 } }
客户端:
public void static main(String[] args){ Target target = new Adapter(); target.sampleOperation();//客户端只需要调用目标方法 }
优点:
两个类所做的事情相同或相似时,但是具有不同的接口使用它们,这样可以统一接口
缺点:
能预防接口不同的问题,及时重构,问题不至于扩大,只有碰到无法改变原有设计和代码的情况时,才考虑适配。最好能在事前控制好
相关文章推荐
- excel文件如何转换成pdf的格式
- 在N个数中找出出现奇数次的数
- TCP的拥塞控制
- tjut 4635
- Httpclient简介
- Redmine性能测试
- R-CNN,SPP-NET, Fast-R-CNN,Faster-R-CNN, YOLO, SSD系列深度学习检测方法梳理
- 给你一个:驱动程序A,数据源名称为B,用户名称为C,密码为D,数据库表为T,请用JDBC检索出表T的所有数据
- DirectX 因素:模拟合成器的仿真
- VMware虚拟机与window共享目录
- 标签
- HDU 5289 Assignment [RMQ区间查询+二分搜索]
- 编译JDK源代码,开启Debug信息
- 如何使用Git推送项目代码到Git仓库
- 深入剖析 OC 中的方法结构 (isa指针的指向,以及元类的概念等)
- 进程和线程
- Unable to simultaneously satisfy constraints.
- Hadoop 技术笔记
- java厚积薄发之java反射机制
- iOS中静态库开发调用函数BUG