设计模式详解
前言:使用设计模式的目的是为了代码重用,避免程序大量修改,同时使代码更容易理解。在没学会设计模式之前,我喜欢申明很多的类,实现很多的借口,很多类之间看似很相似,但总不知道该如何使代码看起来更加简洁,自己写的代码几乎只有自己看得懂,没有可读性,直到学了设计模式,顿时茅塞顿开,下面就我的一些理解和大家一起分享。本文主要讲的是三种常见的设计模式——单例模式、工厂模式和适配器模式。
1、单例模式
单例模式的作用在于保证在整个应用程序的生命周期中,任何一个时刻,单例类的实例最多只有一个。那什么时候需要用到单例模式呢?举个简单的例子,比如实验室里的打印机,实验室10个人都有可能去使用打印机,每个人的计算机都能连接打印机,为了避免两个人的材料同时输出到打印机上,此时打印机就可以使用单例模式,即一台打印机只有一个打印程序的实例。一般来说,单例模式都是自行实例化,并向整个系统提供这个实例单例模式。为了保证系统中只有一个实例,这就需要把单例类的构造函数声明为私有,同时提供一个全局访问点。例如:
public class Test{ //构造函数必须私有 private Test(); private static Test uniqueInstance=new Test(); //此为全局访问点 public static Test getInstance(){ return uniqueInstance; } }
上例也说明了为什么单例模式是自行实例化,因为在使用前对象就已经创建好了(uniqueInstance),要使用只需调用public的getInstance方法即可。因此,单例模式在多线程环境下是试用的。最典型的例子就是Hibernate下的SessionFactory了,因为SessionFactory需要线程安全,能被多个线程同时访问,所有为了方便使用,用单例模式来实现,具体的可以自行去看Hibernate有关的知识点。
2、工厂模式
一般来说,工厂模式专门负责实例化有大量公共接口的类,它可以动态的决定将哪一个类实例化,而不需要事先知道每次要实例化哪一个类。
1)简单工厂模式。简单工厂模式的工厂类是根据提供给他的参数,返回的是可能产品中的一个类的实例。因此,简单工厂模式又称静态工厂方法模式。它存在的目的很简单:定义一个用于创建对象的接口。它的构成包括:
(1) 工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑。在java中它往往由一个具体类实现。
(2) 抽象产品角色:它一般是具体产品继承的父类或者实现的接口。在java中由接口或者抽象类来实现。
(3)具体产品角色:工厂类所创建的对象就是此角色的实例。在java中由一个具体类实现。
比如:
(百度上找的图,自己都呵呵了,仔细看不难发现带三角形箭头的箭头表示继承或实现关系,不带三角形的箭头表示创建关系,及工厂与产品的关系)
从上图我们看到,SimpleFactory既是工厂类角色,它与Machine的关系在于其内部有返回Machine类(即为抽象产品角色)的方法,此方法根据不同的参数返回不同的Machine实现类,此图中返回的就是具体产品角色(Washer、Televisor、Refrigerator)。
public interface Machine { //此处代码省略 } public class Refrigerator implements Machine { //此处代码省略 } public class Televisor implements Machine { //此处代码省略 } public class Washer implements Machine { //此处代码省略 } public class SimpleFactory { public Machine create(int n) { if(n==1)return new Refrigerator(); else if(n==2)return new Televisor(); else return new Washer(); } }
2)工厂方法模式
工厂方法模式是简单工厂模式的进一步抽象化和推广,工厂方法模式里不再只由一个工厂类决定那一个产品类应当被实例化,这个决定被交给抽象工厂的子类去做。它是类的创建模式,其用意是定义一个用于创建产品对象的工厂的接口,而将实际创建工作推迟到工厂接口的子类中。多态的使用,使得工厂方法模式保持了简单工厂模式的优点,而克服了其缺点。其组成如下:
(1)抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。
(2)具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。
(3)抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。
(4)具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现。
先看个图:
(又是百度的图片,哈哈,来者不拒,谁让我懒得画)
上图的带有三角形的箭头的箭头表示实现的关系(concreteCreatorA和B都是实现的接口Creator),虚线箭头表示工厂与产品的关系,即实现产品对象的实例化。从上图我们可以看出来,接口或抽象类Creator即为抽象工厂角色,concreteCreatorA和B是具体工厂角色,Product是抽象产品角色,concreteProductA和B是具体产品角色。Creator实现了对产品的所有操作方法,而不实现产品对象的实例化,所有产品的实例化(concreteProductA和B)由Creator的子类(concreteCreatorA和B)来完成。听起来有点抽象,下面来看看代码。
//抽象产品角色,相当于上述的Product public interface Moveable { void run(); } //具体产品角色,相当于concreteProductA和B public class Plane implements Moveable { @Override public void run() { System.out.println("plane...."); } } public class Broom implements Moveable { @Override public void run() { System.out.println("broom....."); } } //抽象工厂角色,相当于Creator public abstract class VehicleFactory { abstract Moveable create(); } //具体工厂角色,相当于concreteCreatorA和B public class PlaneFactory extends VehicleFactory{ public Moveable create() { return new Plane(); } } public class BroomFactory extends VehicleFactory{ public Moveable create() { return new Broom(); } } //测试类 public class Test { public static void main(String[] args) { VehicleFactory factory = new BroomFactory(); Moveable m = factory.create(); m.run(); } }
从代码应该能比较明显的看出上述四个角色的逻辑关系,这样也就能理解工厂方法模式了。
简单工厂和工厂方法模式的比较:
工厂方法模式和简单工厂模式在定义上的不同是很明显的。工厂方法模式的核心是一个抽象工厂类,而不像简单工厂模式, 把核心放在一个实类上。工厂方法模式可以允许很多实的工厂类从抽象工厂类继承下来, 从而可以在实际上成为多个简单工厂模式的综合,从而推广了简单工厂模式。
反过来讲,简单工厂模式是由工厂方法模式退化而来。设想如果我们非常确定一个系统只需要一个实的工厂类, 那么就不妨把抽象工厂类合并到实的工厂类中去。而这样一来,我们就退化到简单工厂模式了。
3)抽象工厂模式
抽象工厂模式是所有形态的工厂模式中最为抽象和最具一般性的一种形态。它指的是当有多个抽象角色时,抽象工厂模式可以向客户端提供一个接口,使客户端在不必指定产品的具体的情况下,创建多个产品族中的产品对象。在抽象工厂模式中,抽象产品 (AbstractProduct) 可能是一个或多个,从而构成一个或多个产品族(Product Family)。 在只有一个产品族的情况下,抽象工厂模式实际上退化到工厂方法模式。
按惯例,先看抽象工厂模式的设计类图。(实线段表示继承或实现的关系,虚线表示工厂与产品的关系)
AbstractProductA和AbstractProductB各代表一个产品家族,实现这些接口的类代表具体的产品,AbstractFactory为创建产品的接口,能够创建所有产品家族中的所有类型的产品,它的子类(ConcreteFactory1和2)可以根据具体情况创建对应的产品。换句话说,Client需要的只是类型与这些抽象产品角色相同的一些实例,而不是这些抽象产品的实例。可以把上图想象成苹果公司,接口AbstractFactory即为苹果总公司(总公司是抽象的,总公司大楼并不生产产品),而ConcreteFactory1和2可以表示其在亚洲和美洲的生产基地(是具体的,是苹果总公司在亚洲和美洲的具体实现)。而AbstractProductA表示Iphone系列,是抽象的产品家族,假设此系列目前流水线上有两款手机产品一是iPhone6s(ProductA1,主要在亚洲基地ConcreteFactory1生产),另一个是iPhone7(ProductA2,还未在亚洲上市,只能在美洲ConcreteFactory2生产)。AbstractProductA表示Ipad系列,B1在亚洲(ConcreteFactory1)生产,B2在美洲ConcreteFactory2生产。而每个人(Client)要买苹果系列都需要向苹果总公司提交网上申请。此时我想买个iPhone7,那我根本不会关系iPhone7是在哪里生产的,我只需要一个iPhone7而已(即类型与ProductA2相同的一个实例),苹果总公司内部是怎么运作的我不需要知道,但我知道苹果总公司能给我任何它旗下产品族的一个产品。这就是抽象工厂的概念,解释的可能不够严谨,下面再看一个例子。
//抽象工厂类 public abstract class AbstractFactory { public abstract Vehicle createVehicle(); public abstract Weapon createWeapon(); public abstract Food createFood(); } //具体工厂类,其中Food,Vehicle,Weapon是抽象类, public class DefaultFactory extends AbstractFactory{ @Override public Food createFood() { return new Apple(); } @Override public Vehicle createVehicle() { return new Car(); } @Override public Weapon createWeapon() { return new AK47(); } } //测试类 public class Test { public static void main(String[] args) { AbstractFactory f = new DefaultFactory(); Vehicle v = f.createVehicle(); v.run(); Weapon w = f.createWeapon(); w.shoot(); Food a = f.createFood(); a.printName(); } }
这段代码比较简单,没有很复杂的逻辑关系,应该比较好懂。
3.适配器模式
适配器模式也成为变压器模式,它是把一个类的接口转换成客户端期望的另一个接口,从而使原本因接口不匹配而无法一起工作的两个类能够一起工作,适配类可以根据所传递的参数返还一个合适的实例给客户端。
模式中的角色:
目标接口(Target):客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口。
需要适配的类(Adaptee):需要适配的类或适配者类。
适配器(Adapter):通过包装一个需要适配的对象,把原接口转换成目标接口。
比如,现在系统里已经实现了点、线和正方形,而现在客户需要一个圆形,一般的方法是建立一个Circle类来继承Shape类,然后去实现对应的display、fill等方法。但如果此时发现其他项目组其他人已经实现了一个画圆的类,但是他的方法名却和自己不一样(为displayhh,fillhh),则不能直接使用这个类(因为正方形等类都是继承的Shape,display、fill是对shape方法的override,方法名不一样不行,必须保证多态性,即继承的多态性)。此时要是重新写这个类就显得很愚蠢(本例中的Circle类简单,但实际中往往会遇到很复杂的类,真要重写,真是呵呵了)。在上述例子中目标接口即为需要的Circle类,需要适配的类就是已经有的画圆的类。具体看个图和看个例子。
适配器模式的代码实现 /// <summary> /// 定义客户端期待的接口 /// </summary> public class Target { /// <summary> /// 使用virtual修饰以便子类可以重写 /// </summary> public virtual void Request() { Console.WriteLine("This is a common request"); } } /// <summary> /// 定义需要适配的类 /// </summary> public class Adaptee { public void SpecificRequest() { Console.WriteLine("This is a special request."); } } /// <summary> /// 定义适配器 /// </summary> public class Adapter extends Target { // 建立一个私有的Adeptee对象 private Adaptee adaptee = new Adaptee(); /// <summary> /// 通过重写,表面上调用Request()方法,变成了实际调用SpecificRequest() /// </summary>
@Override
public void Request() { adaptee.SpecificRequest(); } } 客户端代码 public class Program { public static void main(string[] args) { // 对客户端来说,调用的就是Target的Request() Target target = new Adapter(); target.Request(); Console.Read(); } }
Circle的例子和这个也是差不多的,具体就不给了,少年们自己去实现吧。