设计模式
Num1:单例模式
基本概念:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
常见写法:
懒汉式
public class Singleton {
/* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */
private static Singleton instance = null;
/* 私有构造方法,防止被实例化 */
private Singleton() {}
/* 1:懒汉式,静态工程方法,创建实例 */
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
调用:
Singleton.getInstance().method();
优点:延迟加载(需要的时候才去加载),适合单线程操作
缺点: 线程不安全,在多线程中很容易出现不同步的情况,如在数据库对象进行的频繁读写操作时。
双重线程检查模式
public class SingletonInner {
private static volatile SingletonInner sInst = null; // <<< 这里添加了 volatile
/**
* 私有的构造函数
*/
private SingletonInner() {}
public static SingletonInner getInstance() {
SingletonInner inst = sInst; // <<< 在这里创建临时变量
if (inst == null) {
synchronized (SingletonInner.class) {
inst = sInst;
if (inst == null) {
inst = new SingletonInner();
sInst = inst;
}
}
}
return inst; // <<< 注意这里返回的是临时变量
}
protected void method() {
System.out.println("SingletonInner");
}
}
调用:
Singleton.getInstance().method();
优点:延迟加载,线程安全
缺点: 写法复杂,不简洁
内部类的实现
public class SingletonInner {
/**
* 内部类实现单例模式
* 延迟加载,减少内存开销
*/
private static class SingletonHolder {
private static SingletonInner instance = new SingletonInner();
}
/**
* 私有的构造函数
*/
private SingletonInner() {}
public static SingletonInner getInstance() {
return SingletonHolder.instance;
}
protected void method() {
System.out.println("SingletonInner");
}
}
调用:
Singleton.getInstance().method();
优点:延迟加载,线程安全(java中class加载时互斥的),也减少了内存消耗,推荐使用内部类方式。
Num2:工厂模式
基本概念:为创建对象提供过渡接口,以便将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。
分为三类:
简单工厂模式
Simple Factory
:不利于产生系列产品;工厂方法模式
Factory Method
:又称为多形性工厂;抽象工厂模式
Abstract Factory
:又称为工具箱,产生产品族,但不利于产生新的产品;
这三种模式从上到下逐步抽象,并且更具一般性。GOF在《设计模式》一书中将工厂模式分为两类:工厂方法模式(Factory Method)与抽象工厂模式(Abstract Factory)。将简单工厂模式(Simple Factory)看为工厂方法模式的一种特例,两者归为一类。
简单工厂模式
简单工厂模式又称静态工厂方法模式。重命名上就可以看出这个模式一定很简单。它存在的目的很简单:定义一个用于创建对象的接口。
在简单工厂模式中,一个工厂类处于对产品类实例化调用的中心位置上,它决定那一个产品类应当被实例化, 如同一个交通警察站在来往的车辆流中,决定放行那一个方向的车辆向那一个方向流动一样。
先来看看它的组成:
- 工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑。在java中它往往由一个具体类实现。
- 抽象产品角色:它一般是具体产品继承的父类或者实现的接口。在java中由接口或者抽象类来实现。
- 具体产品角色:工厂类所创建的对象就是此角色的实例。在java中由一个具体类实现。
示例代码:
public class Factory{ //getClass 产生Sample 一般可使用动态类装载装入类。
public static Sample creator(int which){
if (which==1)
return new SampleA();
else if (which==2)
return new SampleB();
}
}
还有一种目前比较流行的规范是把静态工厂方法命名为valueOf
或者getInstance
。
valueOf
:该方法返回的实例与它的参数具有同样的值,例如:
Integer a=Integer.valueOf(100); //返回取值为100的Integer对象
public class Complex {
private final float re;
private final float im;
private Complex(float re, float im){
this.re = re;
this.im = im;
}
public static Complex valueOf(float re, float im){
return new Complex(re, im);
}
public static Complex valueOfPolar(float r, float theta){
return new Complex((float)(r * Math.cos(theta)), (float)(r * Math.sin(theta)));
}
}
从上面代码可以看出,valueOf()方法能执行类型转换操作,在本例中,把int类型的基本数据转换为Integer对象。
getInstance
:返回的实例与参数匹配,例如:
Calendar cal=Calendar.getInstance(Locale.CHINA); //返回符合中国标准的日历
工厂方法模式
工厂方法模式是简单工厂模式的进一步抽象化和推广,工厂方法模式里不再只由一个工厂类决定那一个产品类应当被实例化,这个决定被交给抽象工厂的子类去做。
来看下它的组成:
- 抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。
- 具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象
- 抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。
- 具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现。
工厂方法模式使用继承自抽象工厂角色的多个子类来代替简单工厂模式中的“上帝类”。正如上面所说,这样便分担了对象承受的压力;而且这样使得结构变得灵活 起来——当有新的产品(即暴发户的汽车)产生时,只要按照抽象产品角色、抽象工厂角色提供的合同来生成,那么就可以被客户使用,而不必去修改任何已有的代 码。可以看出工厂角色的结构也是符合开闭原则的!
示例代码:
//抽象产品角色
public interface Moveable {
void run();
}
//具体产品角色
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.....");
}
}
//抽象工厂
public abstract class VehicleFactory {
abstract Moveable create();
}
//具体工厂
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();
}
}
可以看出工厂方法的加入,使得对象的数量成倍增长。当产品种类非常多时,会出现大量的与之对应的工厂对象,这不是我们所希望的。因为如果不能避免这种情 况,可以考虑使用简单工厂模式与工厂方法模式相结合的方式来减少工厂类:即对于产品树上类似的种类(一般是树的叶子中互为兄弟的)使用简单工厂模式来实 现。
简单工厂和工厂方法模式的比较
工厂方法模式和简单工厂模式在定义上的不同是很明显的。工厂方法模式的核心是一个抽象工厂类,而不像简单工厂模式, 把核心放在一个实类上。工厂方法模式可以允许很多实的工厂类从抽象工厂类继承下来, 从而可以在实际上成为多个简单工厂模式的综合,从而推广了简单工厂模式。
反过来讲,简单工厂模式是由工厂方法模式退化而来。设想如果我们非常确定一个系统只需要一个实的工厂类, 那么就不妨把抽象工厂类合并到实的工厂类中去。而这样一来,我们就退化到简单工厂模式了。
抽象工厂模式
示例代码:
//抽象工厂类
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();
}
}
在抽象工厂模式中,抽象产品 (AbstractProduct) 可能是一个或多个,从而构成一个或多个产品族(Product Family)。 在只有一个产品族的情况下,抽象工厂模式实际上退化到工厂方法模式。
总结
- 简单工厂模式是由一个具体的类去创建其他类的实例,父类是相同的,父类是具体的。
- 工厂方法模式是有一个抽象的父类定义公共接口,子类负责生成具体的对象,这样做的目的是将类的实例化操作延迟到子类中完成。
- 抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无须指定他们具体的类。它针对的是有多个产品的等级结构。而工厂方法模式针对的是一个产品的等级结构。
Num3:建造(Builder)模式
基本概念:是一种对象构建的设计模式,它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
Builder模式是一步一步创建一个复杂的对象,它允许用户可以只通过指定复杂对象的类型和内容就可以构建它们。用户不知道内部的具体构建细节。Builder模式是非常类似抽象工厂模式,细微的区别大概只有在反复使用中才能体会到。
UML结构图:
上图是Strategy 模式的结构图,让我们可以进行更方便的描述:
Builder:为创建一个Product对象的各个部件指定抽象接口。
ConcreteBuilder:实现Builder的接口以构造和装配该产品的各个部件,定义并明确它所创建的表示,提供一个检索产品的接口
Director:构造一个使用Builder接口的对象。
Product:表示被构造的复杂对象。ConcreateBuilder创建该产品的内部表示并定义它的装配过程。
为何使用
是为了将构建复杂对象的过程和它的部件解耦。注意:是解耦过程和部件。
因为一个复杂的对象,不但有很多大量组成部分,如汽车,有很多部件:车轮、方向盘、发动机,还有各种小零件等等,部件很多,但远不止这些,如何将这些部件装配成一辆汽车,这个装配过程也很复杂(需要很好的组装技术),Builder模式就是为了将部件和组装过程分开。
如何使用
首先假设一个复杂对象是由多个部件组成的,Builder模式是把复杂对象的创建和部件的创建分别开来,分别用Builder类和Director类来表示。
首先,需要一个接口,它定义如何创建复杂对象的各个部件:
public interface Builder {
//创建部件A 比如创建汽车车轮void buildPartA();
//创建部件B 比如创建汽车方向盘void buildPartB();
//创建部件C 比如创建汽车发动机void buildPartC();
//返回最后组装成品结果 (返回最后装配好的汽车)
//成品的组装过程不在这里进行,而是转移到下面的Director类中进行.
//从而实现了解耦过程和部件
Product getResult();
}
用Director构建最后的复杂对象,而在上面Builder接口中封装的是如何创建一个个部件(复杂对象是由这些部件组成的),也就是说Director的内容是如何将部件最后组装成成品:
public class Director {
private Builder builder;
public Director( Builder builder ) {
this.builder = builder;
}
// 将部件partA partB partC最后组成复杂对象
//这里是将车轮 方向盘和发动机组装成汽车的过程
public void construct() {
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
}
}
Builder的具体实现ConcreteBuilder:
- 通过具体完成接口Builder来构建或装配产品的部件;
- 定义并明确它所要创建的是什么具体东西;
- 提供一个可以重新获取产品的接口。
public class ConcreteBuilder implements Builder {
Part partA, partB, partC;
public void buildPartA() {
//这里是具体如何构建
}
public void buildPartB() {
//这里是具体如何构建
}
public void buildPartC() {
//这里是具体如何构建
}
public Product getResult() {
//返回最后组装成品结果
}
}
复杂对象:产品Product:
public interface Product { }
复杂对象的部件:
public interface Part { }
我们看看如何调用Builder模式:
ConcreteBuilder builder = new ConcreteBuilder();
Director director = new Director( builder );
director.construct();
Product product = builder.getResult();
Builder模式的应用
在Java实际使用中,我们经常用到"池"(Pool)的概念,当资源提供者无法提供足够的资源,并且这些资源需要被很多用户反复共享时,就需要使用池。"池"实际是一段内存,当池中有一些复杂的资源的"断肢"(比如数据库的连接池,也许有时一个连接会中断),如果循环再利用这些"断肢",将提高内存使用效率,提高池的性能。修改Builder模式中Director类使之能诊断"断肢"断在哪个部件上,再修复这个部件。
Num4:观察者模式
基本概念:观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。观察者模式又叫发布-订阅(Publish/Subscribe)模式。
UML结构图
上图是Observer 模式的结构图,让我们可以进行更方便的描述:
Subject类:它把所有对观察者对象的引用保存在一个聚集里,每个主题都可以有任何数量的观察着。抽象主题提供一个接口,可以增加和删除观察着对象。
Observer类:抽象观察者,为所有的具体观察者定义一个接口,在得到主题的通知时更新自己。
ConcreteSubject类:具体主题,将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。
ConcreteObserver类:具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。
如何使用
例如:老师有电话号码,学生需要知道老师的电话号码以便于在合适的时候拨打,在这样的组合中,老师就是一个被观察者(Subject),学生就是需要知道信息的观察者,当老师的电话号码发生改变时,学生得到通知,并更新相应的电话记录。
先创建一个Subject类:
/**
* Subject(目标,Subject):
* 目标知道它的观察者。可以有任意多个观察者观察同一个目标。
* 提供注册和删除观察者对象的接口。
*/
public interface Subject {
public void attach(Observer mObserver);
public void detach(Observer mObserver);
public void notice();
}
创建Observer类:
/**
* Observer(观察者,Observer):
* 为那些在目标发生改变时需要获得通知的对象定义一个更新接口。
*/
public interface Observer {
public void update();
}
创建ConcreteSubject类:
/**
* ConcreteSubject(具体目标,Teacher)
* 将有关状态存入各ConcreteObserve对象。
* 当他的状态发生改变时,向他的各个观察者发出通知。
*/
public class Teacher implements Subject{
private String phone;
private Vector students;
public Teacher(){
phone = "";
students = new Vector();
}
@Override
public void attach(Observer mObserver) {
students.add(mObserver);
}
@Override
public void detach(Observer mObserver) {
students.remove(mObserver);
}
@Override
public void notice() {
for(int i=0;i<students.size();i++){
((Observer)students.get(i)).update();
}
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
notice();
}
}
创建ConcreteObserver类:
/**
* ConcreteObserver(具体观察者, Student):
* 维护一个指向ConcreteSubject对象的引用。
* 存储有关状态,这些状态应与目标的状态保持一致。
* 实现Observer的更新接口以使自身状态与目标的状态保持一致。
*/
public class Student implements Observer{
private String name;
private String phone;
private Teacher mTeacher;
public Student(String name,Teacher t){
this.name = name;
mTeacher = t;
}
public void show(){
System.out.println("Name:"+name+"\nTeacher'sphone:" + phone);
}
@Override
public void update() {
phone = mTeacher.getPhone();
}
}
客户端测试:
/**
* 观察者(Observer)模式测试类
*/
public class ObserverClient {
public static void main(String[] args) {
Vector students = new Vector();
Teacher t = new Teacher();
for(int i= 0;i<10;i++){
Student st = new Student("Andy.Chen"+i,t);
students.add(st);
t.attach(st);
}
System.out.println("Welcome to Andy.Chen Blog!" +"\n"
+"Observer Patterns." +"\n"
+"-------------------------------");
t.setPhone("12345678");
for(int i=0;i<3;i++)
((Student)students.get(i)).show();
t.setPhone("87654321");
for(int i=0;i<3;i++)
((Student)students.get(i)).show();
}
}
程序运行结果如下:
Welcome to Andy.Chen Blog!
Observer Patterns.
-------------------------------
Name:Andy.Chen0
Teacher'sphone:12345678
Name:Andy.Chen1
Teacher'sphone:12345678
Name:Andy.Chen2
Teacher'sphone:12345678
Name:Andy.Chen0
Teacher'sphone:87654321
Name:Andy.Chen1
Teacher'sphone:87654321
Name:Andy.Chen2
Teacher'sphone:87654321
总结
观察者模式何时适用?
当一个抽象模型有两个方面,其中一个方面依赖于另一方面。将这二者封装在独立的对象中可以使他们各自独立地改变和复用。
当对一个对象的改变需要同时改变其它对象,而不知道具体由多少对象有待改变。
当一个对象必须通知其他对象,而它又不能假定其他对象是谁,换言之,你不希望这些对象是紧密耦合的。让耦合的双方都依赖于抽象,而不是依赖于具体。
Num5:适配器(Adapter)模式
基本概念:适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
适配器模式的用途
用电器做例子,笔记本电脑的插头一般都是三相的,即除了阳极、阴极外,还有一个地极。而有些地方的电源插座却只有两极,没有地极。电源插座与笔记本电脑的电源插头不匹配使得笔记本电脑无法使用。这时候一个三相到两相的转换器(适配器)就能解决此问题,而这正像是本模式所做的事情。
适配器模式的结构
适配器模式有类的适配器模式
和对象的适配器模式
两种不同的形式。
类适配器模式:
在上图中可以看出,Adaptee类并没有sampleOperation2()方法,而客户端则期待这个方法。为使客户端能够使用Adaptee类,提供一个中间环节,即类Adapter,把Adaptee的API与Target类的API衔接起来。Adapter与Adaptee是继承关系,这决定了这个适配器模式是类的:
- 目标(Target)角色:这就是所期待得到的接口。注意:由于这里讨论的是类适配器模式,因此目标不可以是类。
- 源(Adapee)角色:现在需要适配的接口。
- 适配器(Adaper)角色:适配器类是本模式的核心。适配器把源接口转换成目标接口。显然,这一角色不可以是接口,而必须是具体类。
示例代码:
public interface Target {
/**
* 这是源类Adaptee也有的方法
*/
public void sampleOperation1();
/**
* 这是源类Adapteee没有的方法
*/
public void sampleOperation2