创建型设计模式
前言:什么是设计模式
-
设计模式是一套解决软件开发过程中某些常见问题的通用解决方案,是已被反复使用且证明其有效性的设计经验的总结
-
分类如下:
-
创建型模式,共5种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
-
结构型模式,共7种:适配器模式、装饰模式、代理模式、外观模式、桥接模式、组合模式、享元模式
-
行为型模式,共11种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
-
创建型设计模式
- 定义:
创建模式是对类的实例化过程的抽象。一些系统在创建对象时,需要动态地决定怎样创建对象,创建哪些对象,以及如何组合和表示这些对象。
简单工厂模式(静态工厂方法)$\star\star\star$
-
定义:
- 由一个工厂对象决定创建出哪一种产品类的实例,把怎么做出产品的任务交给工厂,客户只要提出要求就行
- 由一个工厂对象决定创建出哪一种产品类的实例,把怎么做出产品的任务交给工厂,客户只要提出要求就行
-
目的:
- 定义一个用于创建对象的接口
- 定义一个用于创建对象的接口
-
组成
- 工厂类(Creator)角色:担任这个角色的是工厂方法模式的核心,含有与应用紧密相关的逻辑。工厂类在客户端的直接调用下创建产品对象,它往往由一个具体Java 类实现
- 抽象产品(Product)角色:担任这个角色的类是工厂方法模式所创建对象的父类,或它们共同拥有的接口。抽象产品角色可以用一个Java 接口或者Java 抽象类实现(Tip:如果模式所产生的具体产品类彼此之间没有共同的业务逻辑,那么抽象产品角色可以由一个Java 接口扮演)
- 具体产品(Concrete Product)角色:工厂方法模式所创建的任何对象都是这个角色的实例,具体产品角色由一个具体Java 类实现
-
一般结构
-
变化(以下是退化)
- 如果系统仅有一个具体产品角色的话,那么就可以省略掉抽象产品角色
- 在有些情况下,工厂角色可以由抽象产品角色扮演
- 如果抽象产品角色已经被省略,而工厂角色就可以与具体产品角色合并。换言之,一个产品类为创建自身的工厂
-
好处:
- 客户端则可以免除直接创建产品对象的责任,而仅仅负责“消费”产品,即实现了客户端类与产品类的解耦
- 客户端则可以免除直接创建产品对象的责任,而仅仅负责“消费”产品,即实现了客户端类与产品类的解耦
-
缺点
- 当产品种类增加时,工厂类的工厂方法也必须随之修改
- 当产品种类增加时,工厂类的工厂方法也必须随之修改
-
对开闭原则的支持
- “ 开–闭 ” 原则要求系统允许当新的产品加入系统中,而无需对现有代码进行修改。这一点对于产品的消费角色是成立的,而对于工厂角色是不成立的。简单工厂角色只在有限的程度上支持 “ 开–闭 ” 原则
- “ 开–闭 ” 原则要求系统允许当新的产品加入系统中,而无需对现有代码进行修改。这一点对于产品的消费角色是成立的,而对于工厂角色是不成立的。简单工厂角色只在有限的程度上支持 “ 开–闭 ” 原则
-
例子
- 抽象类(具体类用继承)
package code; // 产品类 abstract class BMW{ public BMW(){ } } class BMW_1 extends BMW{ public BMW_1() { System.out.println("make a BMW_1"); } } class BMW_2 extends BMW{ public BMW_2() { System.out.println("make a BMW_2"); } } // 工厂类 class factory{ public BMW createBmw(int type){ switch(type) { case 1: return new BMW_1(); case 2: return new BMW_2(); default: break; } return null; } } public class Customer { public static void main(String[] args) { factory f = new factory(); BMW one = f.createBmw(1); BMW t = f.createBmw(2); } }
- 接口(具体类用实现 implements)
package code; // 产品类 interface fruit{ void grow(); void harvest(); void plant(); } class Apple implements fruit{ private int tree_age = 10; public Apple(){ System.out.println("You get a Apple now...\n"); } public void grow(){ System.out.println("Apple is growing...\n"); } public void harvest(){ System.out.println("Apple is harvesting...\n"); } public void plant(){ System.out.println("Apple is planting...\n"); } public int get_tree_age(){ return tree_age; } } class Grap implements fruit{ private boolean seedless = true; public Grap(){ System.out.println("You get a Grap now...\n"); } public void grow(){ System.out.println("Grap is growing...\n"); } public void harvest(){ System.out.println("Grap is harvesting...\n"); } public void plant(){ System.out.println("Grap is planting...\n"); } public boolean get_seedless(){ return seedless; } } // 工厂类 class gardener{ public fruit create(String type){ switch(type) { case "Apple": return new Apple(); case "Grap": return new Grap(); default: break; } return null; } } public class Customer { public static void main(String[] args) { gardener f = new gardener(); fruit a = f.create("Apple"); fruit g = f.create("Grap"); } }
工厂模式(虚拟构造子模式 / 多态性工厂模式)$\star\star\star\star\star$
- 定义:
- 工厂方法模式去掉了简单工厂模式中工厂方法的静态属性,使得它可以被子类继承。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担,即把工厂类抽象出来
- 工厂方法模式去掉了简单工厂模式中工厂方法的静态属性,使得它可以被子类继承。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担,即把工厂类抽象出来
- 目的:
- 弥补简单工厂模式对开闭原则支持的不足
- 弥补简单工厂模式对开闭原则支持的不足
- 组成:
- 抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在 java 中它由抽象类或者接口来实现
- 具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象
- 抽象产品角色:它是具体产品继承的父类或者是实现的接口。在 java 中一般有抽象类或者接口来实现
- 具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在 java 中由具体的类来实现
- 一般结构
- 变化(退化):
- 工厂方法模式退化后可以变得很像简单工厂模式。设想如果非常确定一个系统只需要一个具体工厂类,那么就不妨把抽象工厂类合并到具体的工厂类中去。由于反正只有一个具体工厂类,所以不妨将工厂方法改成为静态方法,这时候就得到了简单工厂模式
- 工厂方法模式退化后可以变得很像简单工厂模式。设想如果非常确定一个系统只需要一个具体工厂类,那么就不妨把抽象工厂类合并到具体的工厂类中去。由于反正只有一个具体工厂类,所以不妨将工厂方法改成为静态方法,这时候就得到了简单工厂模式
- 好处:
- 扩展性高,新增产品时,无需更改任何已有代码
- 扩展性高,新增产品时,无需更改任何已有代码
- 缺点:
- 工厂方法模式仿佛已经很完美的对对象的创建进行了包装,使得客户程序中仅仅处理抽象产品角色提供的接口,但使得对象的数量成倍增长。当产品种类非常多时,会出现大量的与之对应的工厂对象
- 工厂方法模式仿佛已经很完美的对对象的创建进行了包装,使得客户程序中仅仅处理抽象产品角色提供的接口,但使得对象的数量成倍增长。当产品种类非常多时,会出现大量的与之对应的工厂对象
- 对开闭原则的支持
- 工厂角色、消费角色均满足
- 工厂角色、消费角色均满足
- 例子
- 都是接口 例1
package code; // 抽象产品类 abstract class BMW{ } // 具体产品 class BMW_1 extends BMW{ public BMW_1() { System.out.println("make a BMW_1"); } } class BMW_2 extends BMW{ public BMW_2() { System.out.println("make a BMW_2"); } } // 抽象工厂类 interface factoryBMW{ BMW factory(); } // 具体工厂 class factory_BMW1 implements factoryBMW{ public factory_BMW1(){ System.out.println("make a factory_1"); } public BMW_1 factory(){ return new BMW_1(); } } class factory_BMW2 implements factoryBMW{ public factory_BMW2(){ System.out.println("make a factory_2"); } public BMW_2 factory(){ return new BMW_2(); } } public class Customer { public static void main(String[] args) { // 先得到具体工厂,再找具体的工厂得到产品 factory_BMW1 f1 = new factory_BMW1(); BMW b1 = f1.factory(); factory_BMW2 f2 = new factory_BMW2(); BMW b2 = f2.factory(); } }
- 例2
package code; // 产品类 interface fruit{ void grow(); void harvest(); void plant(); } class Apple implements fruit{ private int tree_age = 10; public Apple(){ System.out.println("You get a Apple now...\n"); } public void grow(){ System.out.println("Apple is growing...\n"); } public void harvest(){ System.out.println("Apple is harvesting...\n"); } public void plant(){ System.out.println("Apple is planting...\n"); } public int get_tree_age(){ return tree_age; } } class Grap implements fruit{ private boolean seedless = true; public Grap(){ System.out.println("You get a Grap now...\n"); } public void grow(){ System.out.println("Grap is growing...\n"); } public void harvest(){ System.out.println("Grap is harvesting...\n"); } public void plant(){ System.out.println("Grap is planting...\n"); } public boolean get_seedless(){ return seedless; } } // 工厂类 interface gardener{ fruit factory(); } class factory_apple{ fruit factory(){ return new Apple(); } } class factory_garp{ fruit factory(){ return new Grap(); } } public class Customer { public static void main(String[] args) { factory_apple fa = new factory_apple(); fruit a = fa.factory(); factory_garp fg = new factory_garp(); fruit g = fg.factory(); } }
抽象工厂模式$\star\star\star\star\star$
-
定义:
- 抽象工厂模式是所有形态的工厂模式中最为抽象和最具一般性的一种形态。抽象工厂模式与工厂方法模式的最大区别就在于,工厂方法模式针对的是一个产品等级结构;而抽象工厂模式则需要面对多个产品等级结构
-
目的:
- 处理具有相同(或者相似)等级结构的多个产品族中的产品对象创建问题。也就是让一个工厂可以产生多个不同的产品,而工厂模式在加入新的不同产品时,每次都要加入对应的具体工厂类和产品。
(Tip:等级结构可以理解为同一个对象的不同属性,比如都是圆形,可以有不同的颜色,每个颜色可以看成一个等级。而产品族可以看成是一套产品,比如说套装文具,要有尺子,笔等等不同的对象,那么一套文具,就可以看成是一个产品族)
所以上面这句话的意思是,如果每一套文具都有笔和尺子这两个有各自等级结构(属性)的对象,那么抽象工厂就可以一次生产一套文具(一个产品族),每个产品族有不同的尺子和笔。
一般来说,有多少产品族,就有多少具体工厂,一个对象有多少属性,就有多少产品族
- 处理具有相同(或者相似)等级结构的多个产品族中的产品对象创建问题。也就是让一个工厂可以产生多个不同的产品,而工厂模式在加入新的不同产品时,每次都要加入对应的具体工厂类和产品。
-
组成:
- 抽象工厂(AbstractFactory)角色:担任这个角色的是抽象工厂模式的核心,它与应用系统的实现无关。通常使用Java 接口或者抽象Java 类实现,而所有的具体工厂类必须实现这个Java 接口或继承这个抽象Java 类
- 具体工厂类(Conrete Factory)角色:这个角色直接在客户端的调用下创建产品的实例。这个角色含有选择合适的产品对象的逻辑,而这个逻辑是与应用系统的业务逻辑紧密相关的。通常使用具体 Java 类实现这个角色
- 抽象产品(Abstract Product)角色:担任这个角色的类是抽象工厂模式所创建的对象的父类,或它们共同拥有的接口。通常使用Java 接口或者抽象 Java 类实现这一角色
- 具体产品(Concrete Product)角色:抽象工厂模式所创建的任何产品对象都是某一个具体产品类的实例。这是客户端最终需要的东西,其内部一定实现了应用系统的业务逻辑。通常使用具体Java 类实现这个角色
-
使用场景
- 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节。这对于所有形态的工厂模式都是重要的
- 这个系统的产品有多于一个的产品族,而系统只消费其中某一族的产品
- 同属于同一个产品族的产品是在一起使用的,这一约束必须要在系统的设计中体现出来
-
类图举例:
-
优点:
- 对于相互间要一起使用的对象而言,可以用一个工厂创建出多个需要的对象
- 对于相互间要一起使用的对象而言,可以用一个工厂创建出多个需要的对象
-
缺点:
- 不完全支持 “开-闭原则”
- 不完全支持 “开-闭原则”
-
对开闭原则的支持:
- 增加新的产品族:
---只需要向系统中加入新的具体工厂类就可以了,没有必要修改已有的工厂角色或者产品角色。因此,在系统中的产品族增加时,抽象工厂模式是支持“开-闭”原则的 - 增加新的产品等级结构:
需要修改所有的工厂角色,给每一个工厂类都增加一个新的工厂方法,而这显然是违背“开–闭”原则的。换言之,对于产品等级结构的增加,抽象工厂模式是不支持“开–闭”原则的 - 抽象工厂模式以一种倾斜的方式支持增加新的产品,它为新产品族的增加提供方便,而不能为新的产品等级结构的增加提供这样的方便
- 增加新的产品族:
-
例子:
- Windows and Unix
package code; // 抽象按钮类 abstract class Bottom{ public abstract void paint(); } class Win_Bottom extends Bottom{ public void paint(){ System.out.println("Windows Bottom is painting...\n"); } } class Unix_Bottom extends Bottom{ public void paint(){ System.out.println("Unix Bottom is painting...\n"); } } // 抽象文本类 abstract class Text{ public abstract void paint(); } class Win_Text extends Text{ public void paint(){ System.out.println("Windows Text is painting...\n"); } } class Unix_Text extends Text{ public void paint(){ System.out.println("Unix Text is painting...\n"); } } // 抽象工厂类 abstract class GUI_factory{ // 具体工厂也可以由抽象工厂提供 // public static GUI_factory get_Factory(int GUI){ // if(GUI == 1) // return new Win_factory(); // else // return new Unix_factory(); // } public abstract Bottom get_Bottom(); public abstract Text get_Text(); } // 具体工厂 class Win_factory extends GUI_factory{ public Bottom get_Bottom(){ return new Win_Bottom(); } public Text get_Text(){ return new Win_Text(); } } class Unix_factory extends GUI_factory{ public Bottom get_Bottom(){ return new Unix_Bottom(); } public Text get_Text(){ return new Unix_Text(); } } public class test { public static void main(String[] args) { // GUI_factory f1 = GUI_factory.get_Factory(1); // 如果不需要抽象工厂产生具体工厂,就要客户自己创建具体工厂对象 GUI_factory f1 = new Win_factory(); f1.get_Bottom().paint(); f1.get_Text().paint(); // GUI_factory f2 = GUI_factory.get_Factory(2); GUI_factory f2 = new Unix_factory(); f2.get_Bottom().paint(); f2.get_Text().paint(); } }
单例(Singleton)模式
- 定义:
- 单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类
- 单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类
- 目的:
- 简单来说:同步某些资源。许多时候整个系统只需要拥有一个全局对象,这样有利于我们协调系统整体的行为。
比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。
- 简单来说:同步某些资源。许多时候整个系统只需要拥有一个全局对象,这样有利于我们协调系统整体的行为。
- 组成:
- 应用场景:
- 举一个小例子,在我们的windows桌面上,我们打开了一个回收站,当我们试图再次打开一个新的回收站时,Windows系统并不会为你弹出一个新的回收站窗口。,也就是说在整个系统运行的过程中,系统只维护一个回收站的实例。这就是一个典型的单例模式运用
- 再举一个例子,网站的计数器,一般也是采用单例模式实现,如果你存在多个计数器,每一个用户的访问都刷新计数器的值,这样的话你的实计数的值是难以同步的。但是如果采用单例模式实现就不会存在这样的问题,而且还可以避免线程安全问题
- 类图举例
- 见组成
- 见组成
- 优点:
- 在内存中只有一个对象,节省内存空间
- 避免频繁的创建销毁对象,可以提高性能
- 避免对共享资源的多重占用,简化访问
- 为整个系统提供一个全局访问点
- 缺点
- 不适用于变化频繁的对象
- 不适用于变化频繁的对象
- 例子:
- 饿汉式
public class EagerSingleton{ // 自己创建自己的实例 private static final EagerSingleton my_instance = new EagerSingleton(); // 私有的构造函数,保证外界无法直接实例化 private EagerSingleton() {} public static EagerSingleton get_instance(){ return my_instance; } } 优点:避免了线程同步问题。 缺点:在类装载的时候就完成实例化, 如果从始至终从未使用过这个实例,则会造成内存的浪费。
- 懒汉式
public class LazySingleton{ private static LazySingleton my_instance2 = null; private LazySingleton() {} // 在第一次被引用的时候实例化 synchronized public static LazySingleton get_intance2(){ if(my_instance2 == null) my_instance2 = new LazySingleton(); return my_instance2; } } 优点:单例实例被延迟加载,即只有在真正使用的时候才会实例化一个对象并交给自己的引用 缺点:如果在多线程下,一个线程进入了if (my_instance2 == null) 判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句, 这时便会产生多个实例。所以在多线程环境下不可使用这种方式
原型(Prototype)模式
- 定义:
- 就是复制,相当于自带一个复制函数
- 浅复制:
被复制对象的所有变量都含有与原对象相同的值,而所有的对其它对象的引用都仍然指向原来的对象。浅复制仅仅复制所考虑的对象,而不复制它所引用的对象 - 深复制:
被复制对象的所有变量都含有与原对象相同的值,除去那些引用其它对象的变量。那些引用其它对象的变量将指向复制的新对象。深复制把要复制对象所引用的对象都复制了一遍,而这种对被引用到的对象的复制叫做间接复制
- 浅复制:
- 就是复制,相当于自带一个复制函数
- 目的:
- 通过给出一个原型对象来指明所要创建的对象类型,然后用复制这个原型对象的办法创建出更多的同类型对象
- 通过给出一个原型对象来指明所要创建的对象类型,然后用复制这个原型对象的办法创建出更多的同类型对象
- 组成:
- 客户端(Client)角色:客户类提出创建对象的请求
- 抽象原型(Prototype)角色:这是一个抽象角色,通常由一个java接口或抽象类实现。此角色给出所有的具体原型类所需的接口
- 具体原型(Concrete Prototype)角色:被复制的对象。此角色需要实现抽象原型角色所要求的接口
- 条件:
- 克隆对象与原对象不是同一个对象
- 克隆对象与原对象的类型一样
- 类图举例:
- 优点:
- 简化了对象的创建过程
- 在有大对象或多个对象的复制时,可提高系统性能
- 缺点:
- 每一个类必须额外增加一个克隆方法
- 深复制的实现较为复杂
- 例子:
package code; class PandaToClone{ private int hight, weight, age; public PandaToClone(int h, int w){ this.age = 0; this.hight = h; this.weight = w; } // 注意返还的值的类型必需是Object public Object clone(){ PandaToClone temp = new PandaToClone(this.hight, this.weight); temp.set_age(this.age); return (Object)temp; } public void set_age(int a){ this.age = a; } public int get_age(){ return this.age; } public int get_hight(){ return this.hight; } public int get_weight(){ return this.weight; } } public class test { public static void main(String[] args) { // 创建被复制的对象 PandaToClone this_panda = new PandaToClone(1, 2); this_panda.set_age(3); // 使用自带的复制函数复制一个新对象 PandaToClone that_panda = (PandaToClone)this_panda.clone(); // 检查 System.out.println("age of this panda: " + this_panda.get_age()); System.out.println("hight of this panda: " + this_panda.get_hight()); System.out.println("weight of this panda: " + this_panda.get_weight()); System.out.println("age of that panda: " + that_panda.get_age()); System.out.println("hight of that panda: " + that_panda.get_hight()); System.out.println("weight of that panda: " + that_panda.get_weight()); } }