常听前辈们提起设计模式,可一看 23 种设计模式,被这个数字都吓着了,更别说全部搞懂了,既然前辈们都说的那么重要了,静思之下,决定跟设计模式硬磕到底。
根据用途我们又可以分为三大类,分别为创建型模式、结构型模式和行为型模式。
创建型模式主要关注对象的创建过程,将对象的创建过程进行封装,使客户端可以直接得到对象,而不用去关心如何创建对象。就像一个自行车厂商的角色,我想要生产摩拜单车,永久单车还是哈罗单车,直接找这个厂商生产就行了,生产出来交给客户端去使用,而这个生产过程就是怎么造轮子怎么组装怎么拼接的过程就是对象的创建过程了。
为什么我们要用创建型模式?它给我们带来了什么样的好处?什么样的场景下我们会使用它?
创建型模式
创建型模式是处理对象创建的设计模式,试图根据实际情况使用合适的方式创建对象。
基本的对象创建方式可能会导致设计上的问题,或增加设计的复杂度。创建型模式通过以某种方式控制对象的创建来解决问题。
Java 中创建型的设计模式?工厂模式、抽象工厂、单例模式、原型模式、建造者模式
工厂模式
为什么使用工厂模式?它又有什么好处?什么情况下会使用工厂模式呢?
为了解耦:把对象的创建和使用的过程分开。就是 Class A 想调用 Class B,那么只是调用 B 的方法,而至于 B 的实例化,就交给工厂类。工厂模式可以降低代码重复。如果创建 B 过程都很复杂,需要一定的代码量,而且很多地方都要用到,那么就会有很多的重复代码。可以把这些创建对象 B 的代码放到工厂里统一管理。既减少了重复代码,也方便以后对 B 的维护。
优点:首先,将组装对象的过程封装到一个单独的类中,这样,既不会增加对象间的耦合,又可以最大限度的减小客户端的负担。其次,可以实现面向抽象编程。客户端要求的只是一个抽象的类型,具体返回什么样的对象,由创建者来决定。再次,创建者可以对创建的过程进行优化,例如在特定条件下,使用单例模式或者是使用原型模式,都可以优化系统的性能。
使用场景:对象的创建过程/实例化准备工作很复杂,需要很多初始化参数,查询数据库等。类本身有好多子类,这些类的创建过程在业务中容易发生改变,或者对类的调用容易发生改变。
说了这么多,简单概括一句话:所有的创建模式本质上都是对对象的创建过程进行封装。
简单工厂模式
public class BicycleFactory { public static Bicycle makeBicycle(String name) { if (name.equals("xiaolan")) { Bicycle xiaolan = new XiaolanBicycle(); xiaolan.addColour("blue"); return xiaolan; } else if (name.equals("mobai")) { Bicycle mobai = new MobaiBicycle(); mobai.addColour("orange"); return mobai; } else { return null; } }}
调用:其中 XiaolanBicycle 和 MobaiBicycle 都继承自 Bicycle 类。你想要生产小蓝还是摩拜只要传不同的参数就行了,它会根据你的需要返回派生自同一个父类(或实现同一接口)的实例对象。
工厂模式
我们要使用小汽车,于是创建了个生产汽车的工厂,定义了一个 Car 的接口,用 CarFactory 的工厂类来生产汽车,FactoryTest 测试通过 CarFactory 来获取 Car,它将向 CarFactory 传递信息(BMW/ Cadillac/ DaBen),以便获取它所需对象的类型。
接下来看一下代码实习和演示效果:
/** * 汽车接口 */public interface Car { void getLogo();}
/** * 宝马车 */public class BMWCar implements Car { public void getLogo() { System.out.println("宝马"); }}
/** * 凯迪拉克 */public class CadillacCar implements Car { public void getLogo() { System.out.println("凯迪拉克"); }}
/** * 大奔 */public class DabenCar implements Car { public void getLogo() { System.out.println("奔驰"); }}
/** * 汽车工厂类 */public class CarFactory { // 使用 getCar 方法获取车的对象 public Car getCar(String name) { if (name == null) { return null; } if (name.equalsIgnoreCase("BMW")) { return new BMWCar(); } else if (name.equalsIgnoreCase("Cadillac")) { return new CadillacCar(); } else if (name.equalsIgnoreCase("DaBen")) { return new DabenCar(); } return null; }}
/** 1. 测试类 */public class FactoryTest { public static void main(String[] args) { CarFactory carFactory=new CarFactory(); // 获取 car 的对象,并调用它的 getLogo 方法 Car car1=carFactory.getCar("BMW"); car1.getLogo(); Car car2=carFactory.getCar("Cadillac"); car2.getLogo(); Car car3=carFactory.getCar("DaBen"); car3.getLogo(); }
执行结果:
使用场景: 1、日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。 2、设计一个连接服务器的框架,需要三个协议,"POP3"、"IMAP"、"HTTP",可以把这三个作为产品类,共同实现一个接口。
抽象工厂模式
当提到抽象工厂时,就涉及到了产品族。一个典型的例子,就是电脑厂商制造的电脑,你的笔记本内存条无法用到台式机(PcServer)上,如果我们是个电脑外行那么去选择硬件自己组装电脑了会出现什么结果呢?
调用:
// 得到 Intel 的 CPUCPUFactory cpuFactory = new IntelCPUFactory();CPU cpu = intelCPUFactory.makeCPU();// 得到 AMD 的主板MainBoardFactory mainBoardFactory = new AmdMainBoardFactory();MainBoard mainBoard = mainBoardFactory.make();// 组装 CPU 和主板Computer computer = new Computer(cpu, mainBoard);
单独看 CPU 工厂和主板工厂,它们分别是前面我们说的工厂模式。这种方式也容易扩展,因为要给电脑加硬盘的话,只需要加一个 HardDiskFactory 和相应的实现即可,不需要修改现有的工厂。
但是,这种方式有一个问题,一个厂家是生产笔记本电脑的 cpu 的,一个是生产的 PcServer 的主板,你买来后发现拼不到一个电脑上,这时候还在怀疑是不是组装电脑的教程错了。
这就是产品族的概念,它代表了组成某个产品的一系列附件的集合,你要组装一台笔记本电脑,就要电脑的主板,cpu,硬盘都是配套的,我们直接定义电脑工厂,每个电脑工厂负责生产所有的设备。
这个时候,对于客户端来说,不再需要单独挑选 CPU 厂商、主板厂商、硬盘厂商等,直接选择一家品牌工厂,品牌工厂会负责生产所有的东西,而且能保证肯定是兼容可用的。
public static void main(String[] args) { // 第一步就要选定一个“大厂” ComputerFactory cf = new AmdFactory(); // 从这个大厂造 CPU CPU cpu = cf.makeCPU(); // 从这个大厂造主板 MainBoard board = cf.makeMainBoard(); // 从这个大厂造硬盘 HardDisk hardDisk = cf.makeHardDisk(); // 将同一个厂子出来的 CPU、主板、硬盘组装在一起 Computer result = new Computer(cpu, board, hardDisk);}
优点:当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
缺点:产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。
使用场景: 1、QQ 换皮肤,一整套一起换。 2、生成不同操作系统的程序。
单例模式
懒汉式 :线程安全,所谓懒汉就是你需要的时候我再创建。
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率
public class Singleton { // 静态属性获取实例 private static Singleton instance; private Singleton (){} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
饿汉式:线程安全,我先创建对象,谁饿谁先吃。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; } }
双检锁/双重校验锁
描述:这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } }
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。主要解决:一个全局使用的类频繁地创建与销毁。使用场景:当您想控制实例数目,节省系统资源的时候。
建造者模式
建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。使用场景:多个部件或者零件,都可以装配到一个对象中,但是产生的运行结果又相同。产品类非常复杂,或者产品类中调用顺序不同产生了不同的作用。初始化一个对象特别复杂,如使用多个构造方法,或者说有很多参数,并且都有默认值时。实现:(1) 产品角色:包含多个组成部件的复杂对象。
class Product{ private String partA; private String partB; private String partC; public void setPartA(String partA) { this.partA=partA; } public void setPartB(String partB) { this.partB=partB; } public void setPartC(String partC) { this.partC=partC; } public void show() { //显示产品的特性 }}
(2) 抽象建造者:包含创建产品各个子部件的抽象方法。
abstract class Builder{ //创建产品对象 protected Product product=new Product(); public abstract void buildPartA(); public abstract void buildPartB(); public abstract void buildPartC(); //返回产品对象 public Product getResult() { return product; }}
(3) 具体建造者:实现了抽象建造者接口。
public class ConcreteBuilder extends Builder{ public void buildPartA() { product.setPartA("建造 PartA"); } public void buildPartB() { product.setPartA("建造 PartB"); } public void buildPartC() { product.setPartA("建造 PartC"); }}
(4) 指挥者:调用建造者中的方法完成复杂对象的创建。
class Director{ private Builder builder; public Director(Builder builder) { this.builder=builder; } //产品构建与组装方法 public Product construct() { builder.buildPartA(); builder.buildPartB(); builder.buildPartC(); return builder.getResult(); }}
(5) 客户类。
public class Client{ public static void main(String[] args) { Builder builder=new ConcreteBuilder(); Director director=new Director(builder); Product product=director.construct(); product.show(); }}
原型模式
原型(Prototype)模式:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。例如,Windows 操作系统的安装通常较耗时,如果复制就快了很多。在生活中复制的例子非常多,这里不一一列举了。原型模式的结构与实现由于 Java 提供了对象的 clone() 方法,所以用 Java 实现原型模式很简单。
- 模式的结构原型模式包含以下主要角色。抽象原型类:规定了具体原型对象必须实现的接口。具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。访问类:使用具体原型类中的 clone() 方法来复制新的对象。
其结构图
实现原型模式的克隆分为浅克隆和深克隆,Java 中的 Object 类提供了浅克隆的 clone() 方法,具体原型类只要实现 Cloneable 接口就可实现对象的浅克隆,这里的 Cloneable 接口就是抽象原型类。其代码如下:
//具体原型类class Realizetype implements Cloneable{ Realizetype() { System.out.println("具体原型创建成功!"); } public Object clone() throws CloneNotSupportedException { System.out.println("具体原型复制成功!"); return (Realizetype)super.clone(); }}
//原型模式的测试类public class PrototypeTest{ public static void main(String[] args)throws CloneNotSupportedException { Realizetype obj1=new Realizetype(); Realizetype obj2=(Realizetype)obj1.clone(); System.out.println("obj1==obj2?"+(obj1==obj2)); }}
程序的运行结果如下:
具体原型创建成功!具体原型复制成功!obj1==obj2?false
本文首发于 GitChat,未经授权不得转载,转载需与 GitChat 联系。
阅读全文: http://gitbook.cn/gitchat/activity/5d831abc92b6fc25da7996e8
您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。