Java设计模式的归纳及练习

来源:互联网 发布:php继承 编辑:程序博客网 时间:2024/06/06 14:25

Java作为一门面向对象语言,其设计模式是有别于传统的面向过程语言的,为了更好地规范化Java设计流程,方便项目开发和产品迭代,前人总结了一些优秀的设计模式。

楼主发现自己目前水平对设计模式的理解还不够,先积累经验以后再开坑。


设计模式是什么

设计模式就是软件设计中的成熟实践方案,是由软件开发人员在长期的实践中总结的对一些场景的通用解决方案。

常见的设计模型

类别 设计模式 创建型模式 工厂模式(Factory Pattern)
抽象工厂模式(Abstract Factory Pattern)
单例模式(Singleton Pattern)
建造者模式(Builder Pattern)
原型模式(Prototype Pattern) 结构型模式 适配器模式(Adapter Pattern)
桥接模式(Bridge Pattern)
过滤器模式(Filter、Criteria Pattern)
组合模式(Composite Pattern)
装饰器模式(Decorator Pattern)
外观模式(Facade Pattern)
享元模式(Flyweight Pattern)
代理模式(Proxy Pattern) 行为型模式 责任链模式(Chain of Responsibility Pattern)
命令模式(Command Pattern)
解释器模式(Interpreter Pattern)
迭代器模式(Iterator Pattern)
中介者模式(Mediator Pattern)
备忘录模式(Memento Pattern)
观察者模式(Observer Pattern)
状态模式(State Pattern)
空对象模式(Null Object Pattern)
策略模式(Strategy Pattern)
模板模式(Template Pattern)
访问者模式(Visitor Pattern)


创建型模式提供了一种在创建对象的同时隐藏创建逻辑的方式;结构型模式关注类和对象的组合;行为型模式特别关注对象之间的通信。

此外还有J2EE模式,J2EE模式特别关注表示层。

设计模式的六大原则[1]

  • 1、开闭原则(Open Close Principle)
    开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
  • 2、里氏代换原则(Liskov Substitution Principle)
    里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
  • 3、依赖倒转原则(Dependence Inversion Principle)
    这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。
  • 4、接口隔离原则(Interface Segregation Principle)
    这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。
  • 5、迪米特法则,又称最少知道原则(Demeter Principle)
    最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
  • 6、合成复用原则(Composite Reuse Principle)
    合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。

工厂模式

一只叫Phil的猫主子平日养尊处优,竟产生了猫高于人的错觉,为了征服世界,解放全猫类,它决心建立一支军队。

可是建立军队就得有武器啊,怎样才能得到充足的武器呢?Phil想到了建立军工厂,可是主人家只有一个车库,怎样才能实现多种武器在同一个车库有序生产呢?

这就得靠工厂模式了!

工厂模式,就是定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。

简单来说,就是解决同一个接口的选择问题。当我们在不同条件下创建不同的实例时,工厂模式是一个很典型的选择。

此外还有多元工厂模式和静态模式,详情可以见参考资料。

那工厂模式怎么实现呢?

工厂模式实现方法:子类实现工厂接口。

实现步骤:
1、 创建接口
2、创建实现接口的子类
3、创建一个工厂,生成基于给定信息的实体类的对象
4、验证:使用该工厂,通过传递类型信息来获取实体类的对象

Phil现在非常高兴,它决定用工厂模式改造车库,生产枪和火箭炮两种武器。

//1、创建接口public interface Factory_interface{    public void product();}//创建实现接口的子类//生产枪public class Gun implements Factory_interface {    public void product(){        System.out.println("生产了一杆枪");    }}//生成火箭炮public class Bazooka implements Factory_interface {    public void product(){        System.out.println("生产了一门火箭炮.");    }}//3、创建一个工厂public class Factory {    public Factory_interface productWeapon(String weaponType){        if(weaponType.equals("Gun")){            return new Gun();        }else if( weaponType.equals("Bazooka")){            return new Bazooka();        }else{            System.out.println("请输入正确的武器类型");            return null;        }    }}//4、验证工厂public class Product_Test{    public static void main(String[] args) {        Factory garage = new Factory();        Factory_interface weapon1 = garage.productWeapon("Gun");        weapon1.product();        Factory_interface weapon2 = garage.productWeapon("Bazooka");        weapon2.product();    }}

输出结果:

Compiling Product_Test.java.......-----------OUTPUT-----------生产了一杆枪生产了一门火箭炮.

试生产结果很好,现在车库里可以生成枪和火箭炮了,Phil非常高兴,离猫咪征服世界的目标又进了一步了。下一步,它还要生产出更多的武器。


抽象工厂模式

Phil在自家车库搞出的动静惊动了两边隔壁的英国短尾猫Michael,美国短尾猫Tom和中国三花猫Lilei,它们对于工厂模式的有效都表现了惊叹。

“加入我吧,我们一起开创猫的历史!”在Phil的怂恿下,Michael,Tom和Lilei也上了贼船,三只猫分别占据了自家的车库,也要用工厂模式生产武器。

有了四个工厂,Phil准备干票大的,它准备生产导弹。可是导弹的发射车和弹体得两个工厂共同生产,而且猫咪I号导弹和猫咪II号导弹的发射车是不同的。

这怎么办呢?工厂模式肯定是不适用了,怎样协调几个工厂的生产关系呢?

Phil想到了抽象工厂模式!

抽象工厂模式,可以提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

简单来说,抽象工厂模式就是解决当系统有多于一套的产品,而系统只消费其中某一套的产品的情况下接口选择的问题。

以上面为例,猫咪I号导弹必须要猫咪I号发射车,猫咪II号导弹必须要猫咪II号发射车。抽象工厂解决的就像成套的上衣和裤子怎么生产的问题。

那抽象工厂模式怎么实现呢?

抽象工厂模式实现方法:

实现步骤:
1、建立工厂接口(相当于产品的父类)
2、建立工厂接口的接口(相当于工厂的父类)
3、实现工厂接口(实现各个产品)
4、实现工厂接口的接口(实现各个工厂)
5、验证:创建总工厂,调用各工厂生产套件

抽象工厂既系统又容易拓展,有了这个方法,Phil非常高兴,它下令Michael和Tom分别生产猫咪一号的弹体和发射车,它和Lilei分别生产猫咪二号的弹体和发射车,最后组装成猫咪一号和猫咪二号导弹。

//1、建立工厂接口(相当于产品的父类)//导弹接口public interface Missile{    public String productMissile();}//发射车接口public interface Launcher{    public String productLauncher();}//2、建立工厂接口的接口(相当于工厂的父类)//导弹工厂生产导弹和发射车public interface MissileFactory{    public Missile getMissile();    public Launcher getLauncher();}//3、实现工厂接口(实现各个产品)//猫咪一号弹体public class Missile1 implements Missile {    public String productMissile(){        return "猫咪I号弹体生成完成";    }}//猫咪二号弹体public class Missile2 implements Missile {    public String productMissile(){        return "猫咪II号弹体生成完成";    }}//猫咪一号发射车public class Launcher1 implements Launcher {    public String productLauncher(){        return "猫咪I号发射车生成完成";    }}//猫咪二号发射场public class Launcher2 implements Launcher {    public String productLauncher(){        return "猫咪II号发射车生成完成";    }}//4、实现工厂接口的接口(实现各个工厂)//猫咪一号导弹工厂public class Cat1 implements MissileFactory{    public Missile getMissile(){        return new Missile1();    }    public Launcher getLauncher(){        return new Launcher1();    }}//猫咪二号导弹工厂public class Cat2 implements MissileFactory{    public Missile getMissile(){        return new Missile2();    }    public Launcher getLauncher(){        return new Launcher2();    }}//5、验证:创建总工厂,调用各工厂生产套件//试生产两种导弹public class ProductTest{    public void product(MissileFactory m){        System.out.println("导弹生产:"+m.getMissile().productMissile()+","+m.getLauncher().productLauncher()+"。导弹生成完成!");    }    public static void main(String[] args) {        ProductTest test = new ProductTest();           MissileFactory misslie1 = new Cat1();        MissileFactory misslie2 = new Cat2();        test.product(misslie1);        test.product(misslie2);    }}

输出结果:

导弹生产:猫咪I号弹体生成完成,猫咪I号发射车生成完成。导弹生成完成!导弹生产:猫咪II号弹体生成完成,猫咪II号发射车生成完成。导弹生成完成!

没想到导弹试生产大成功,众猫欢欣鼓舞,庆祝猫咪解放事业取得重大进展。但是看着天台升起的蘑菇云,Phil却陷入了沉思。


单例模式

Phil意识到了一个重要的问题:导弹作为一门强力的战略性武器,不能落入其他人的手中。为了防止自己睡觉或者吃饭时导弹落入人类或者愚蠢的狗手中,必须建立一套身份认证系统,这样别人就算拿到了导弹也发射不了。

那么这个身份认证系统应该怎么设计呢?首先,这个身份系统应该只认可Phil一只猫的命令,其次,这个身份系统在导弹一出厂时就应该预置,不能出厂后再设置。

说起来好像简单,设计起来却不简单,怎么样才能让系统在防止外来注册的同时完成预置注册。Phil毕竟是心怀大志的猫,很快就找到了这个问题的解决方法——单例模式。

单例模式,可以保证一个类仅有一个实例,并提供一个访问它的全局访问点。

直接地说,就是创建一个单例类,该单例类只能有一个实例,必须在类中自己创建自己的唯一实例,而且该实例必须提供给其他所有对象。这三点就是单例模式设计的要求。

本例只是举个例子,单例模式除了防止冲突的作用,还可以节省系统资源,控制实例数目,尤其是一个全局使用的类被频繁地创建与销毁时。

那单例模式怎么实现呢?

单例模式实现方法:构造函数私有化。

实现步骤:
1、 类中私有化创建对象(用private static私有化)
2、构造函数私有化
3、提供公有的对象获取接口,判断系统是否已经有这个单例,如果有则不返回,如果没有则返回对象
4、验证测试

Phil决定用单例模式设计这个只允许司令下达发射命令的身份系统,这是它初步实现的结果。

public class IdentitySystem{    //私有化对象(私有化 pricate static)    private static IdentitySystem commdander;    //构造函数私有化    private IdentitySystem(){};    //获取对象(公有)    public static synchronized IdentitySystem getInstance(){        if(commdander == null){            commdander = new IdentitySystem();        }        return commdander;    }    public void launch(){        System.out.println("身份认证成功!导弹发射!");    }}public class LaunchTest{    public static void main(String[] args) {        IdentitySystem phil = IdentitySystem.getInstance();        //注意这里        IdentitySystem michael = IdentitySystem.getInstance();        phil.launch();    }}

输出结果:

身份认证成功!导弹发射!

注意到上面的Michael没有,编译并没有报错而且Michael实际上也可以调用launch方法。难道单例模式没有实现单例吗?不是的,单例模式说的是实例唯一,上面的phil和micheal是两个引用,但它们共同指向了同一个对象commander,所以在内存上还是唯一的。也就是说单例不是只让Phil发射,而是只让司令员发射,如果两只猫共同担任司令员,那么他们在登录系统时都是以同一个身份登录,共享一个日志。

此外,除了上述的基本懒汉模式单例,在考虑线程安全和执行效率上,单例模式还有饿汉模式,线程安全型懒汉模式,双检锁,登记式,枚举等方法,详情可参见参考资料[1]。

导弹系统的身份认证成功,Phil现在确保了导弹将掌握在猫咪手中了。但是光有武器还不够,还得有骁勇善战的猫咪士兵,Phil决定募集猫咪大军。


建造者模式

周末,Phil在公园空地做了一场慷慨激昂的演讲,众猫群情激愤,纷纷表示要跟着Phil打江山。

士兵是征集到了,武器又怎么分配呢:现在枪有M16和AK-47,掩护用手榴弹有闪光弹和烟雾弹,为了防止军火外流,需要对每次的分配情况做记录,但是组合是多种多样,并不是一整套的,怎么样才能系统地对武器分配作出记录呢?

Phil毕竟是三军统帅,小爪一挥——”我们就用建造者模式“。

建造者模式是将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。

简单来说,建造者模式就是用于一些基本部件不会变,而其组合经常变化的时候。就是我们所说的”套餐”如何组合的问题。

那么怎么样实现建造者模式呢?

建造者模式实现方法:将变与不变分离开。

通过创建和提供实例的部分与管理建造出来的实例的依赖关系的部分分开,我们就可以将变与不变分开。

实现步骤:
1、创建不变的实体类
2、实现依赖关系的控制类
3、验证功能,调用控制类,实现组合

在Phil的带领下,武器分配工作有条不紊地进行着。
(下面代码设计的不太好,用ArrayList应该更恰当一些,以后想到更恰当的描述再重写一遍)

1、创建不变的实体//枪public interface Gun {    public String equipWithGun();}//手榴弹public interface Grenade {    public String equipWithGrenade();}//实体类public class Ak47 implements Gun {    @Override    public String equipWithGun() {        return "AK-47";    }}public class M16 implements Gun{    @Override    public String equipWithGun() {        return "M16";    }}public class FlashBomb implements Grenade{    @Override    public String equipWithGrenade() {        return "flash bomb";    }}public class SmokeBomb implements  Grenade{    @Override    public String equipWithGrenade() {        return "smoke bomb";    }}//2、创建依赖关系的控制类public class WeaponBuilder {    public void weaponGear1(){        System.out.println("Soldier is equiped with "+new Ak47().equipWithGun()+ " and "+new FlashBomb().equipWithGrenade());    }    public void weaponGear2(){        System.out.println("Soldier is equiped with "+new Ak47().equipWithGun()+ " and "+new SmokeBomb().equipWithGrenade());    }    public void weaponGear3(){        System.out.println("Soldier is equiped with "+new Ak47().equipWithGun()+ " and "+new FlashBomb().equipWithGrenade());    }    public void weaponGear4(){        System.out.println("Soldier is equiped with "+new M16().equipWithGun()+ " and "+new SmokeBomb().equipWithGrenade());    }}//3、验证public class SoldierEquipment {    public static void main(String[] args){        WeaponBuilder soldier = new WeaponBuilder();        soldier.weaponGear1();        soldier.weaponGear2();        soldier.weaponGear3();        soldier.weaponGear4();    }}

输出结果:

Soldier is equiped with AK-47 and flash bombSoldier is equiped with AK-47 and smoke bombSoldier is equiped with AK-47 and flash bombSoldier is equiped with M16 and smoke bomb

武器的分配工作井然有序地进行着,很快Phil的部队就变得人员齐整了,但是士兵的训练还有待展开,看着下面的猫咪大军,Phil感到猫咪解放运动仍然任重道远。


原型模式

有了兵力了,还需要对猫军进行科学的训练,提高其纪律性,令其做到纪律严明,整齐划一。

但是猫们本来在家里都娇生惯养惯了,一个个懒散得不行,为了让它们做到行动一致,Phil决定使用模型模式,给他们一个标杆,让他们都和标杆达到一模一样的水准,整个队伍的纪律性不就非常高了吗?

那么什么事原型模式呢?

原型模式可以建立一个可供复制的原型。原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

原型使用场景:1、当一个系统应该独立于它的产品创建,构成和表示时。 2、当要实例化的类是在运行时刻指定时,例如,通过动态装载。 3、为了避免创建一个与产品类层次平行的工厂类层次时。 4、当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。

那么怎么实现原型模式呢?

原型模式实现模式:实现克隆操作,在 JAVA 继承 Cloneable,重写 clone()

原型模式利用已有的一个原型对象,快速地生成和原型对象一样的实例。

实现步骤:
1、实现Cloneable接口
2、实现实体类
3、测试,克隆对象

Phil把表现最好的Lilei作为陆军总教练,要全陆军达到Lilei的水平。

//1、实现Cloneable接口public abstract class SoldierPrototype implements Cloneable{    String soldier;    public void setSoldier(String soldier) {this.soldier = soldier;}    public String getSoldier() {return this.soldier;}    //重写clone    public Object clone()    {        Object object = null;        try {            object = super.clone();        } catch (CloneNotSupportedException exception) {            System.err.println("AbstractSoldier is not Cloneable");        }        return object;    }    public void shoot(){        System.out.println( this.getSoldier()+" can shoot.");    }}//实现具体类public class LandSoldier extends SoldierPrototype {}//测试public class SoldierTraining {    public static void main(String[] args){        SoldierPrototype Lilei = new LandSoldier();        Lilei.setSoldier("Land Soldier");        SoldierPrototype cat1 = (SoldierPrototype) Lilei.clone();        cat1.shoot();    }}

输出结果:

Land Soldier can shoot.

在Lilei的训练下,陆军的水平有了显著的进步,大家都达到了总教头Lilei的水平。


适配器模式

眼看军队建设工作蒸蒸日上,武器生产有条不紊地进行着,猫咪训练也日渐走上正轨。Phil觉得是时候举行一次演习来对猫咪大军做一次正式动员。

但是Phil遇到了一个尴尬的问题:Phil太矮了,站在巡视车上头都冒不出来。


参考资料:

[1] Runoob学院,设计模式菜鸟教程,http://www.runoob.com/design-pattern/design-pattern-tutorial.html
[2] 实验楼,Java进阶之设计模式,https://www.shiyanlou.com/courses/100
[3] 弗里曼,Head First 设计模式(中文版),https://book.douban.com/subject/2243615/,中国电力出版社
[4] 真实的归宿,设计模式原则详解,http://blog.csdn.net/hguisu/article/details/7571617#comments
[5] 退思园,Java开发中的23种设计模式详解,http://blog.csdn.net/doymm2008/article/details/13288067#comments
[6] Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides,设计模式(中文版),https://book.douban.com/subject/1052241/ ,机械工业出版社

原创粉丝点击