对象与对象之间的四种关系

  • 依赖关系(Dependency)
    所谓依赖就是某个对象的功能依赖于另外的某个对象,而被依赖的对象只是作为一种工具在使用,而并不持有对它的引用
    举例:一个人自创生就需要不停的呼吸,而人的呼吸功能之所以能维持生命就在于吸进来的气体发挥了作用,所以说空气只不过是人类的一个工具,而人并不持有对它的引用。
  • 关联关系(Association)
    某个对象会长期的持有另一个对象的引用,而二者的关联往往也是相互的。
    关联的两个对象彼此间没有任何强制性的约束,只要二者同意,可以随时解除关系或是进行关联,
    它们在生命期问题上没有任何约定。被关联的对象还可以再被别的对象关联,所以关联是可以共享的。
    举例:人从生至死都在不断的交朋友,然而没有理由认为朋友的生死与我的生死有必然的联系,故他们的生命期没有关联,我的朋友又可以是别人的朋友,所以朋友可以共享。
  • 聚合关系(Aggregation)
    聚合是强版本的关联。
    它暗含着一种所属关系以及生命期关系。
    被聚合的对象还可以再被别的对象关联,所以被聚合对象是可以共享的。
    虽然是共享的,聚合代表的是一种更亲密的关系。
    举例:我的家和我之间具有着一种强烈的所属关系,
    我的家是可以分享的,而这里的分享又可以有两种。
    其一是聚合间的分享,这正如你和你媳妇儿都对这个家有着同样的强烈关联;
    其二是聚合与关联的分享,如果你的朋友来家里吃个便饭,估计你不会给他配一把钥匙。
  • 组合关系(Compostion)
    组合是关系当中的最强版本,它直接要求包含对象对被包含对象的拥有以及包含对象与被包含对象生命期的关系。
    被包含的对象还可以再被别的对象关联,
    所以被包含对象是可以共享的,
    然而绝不存在两个包含对象对同一个被包含对象的共享。
    举例:组合关系就是整体与部分的关系,部分属于整体,整体不存在,部分一定不存在,然而部分不存在整体是可以存在的,说的更明确一些就是部分必须创生于整体创生之后,而销毁于整体销毁之前。

23种设计模式(8):观察者模式

定义:定义对象间一种一对多的依赖关系,使得当每一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新。

类型:行为类模式

类图:

在软件系统中经常会有这样的需求:如果一个对象的状态发生改变,某些与它相关的对象也要随之做出相应的变化。比如,我们要设计一个右键菜单的功能,只要在软件的有效区域内点击鼠标右键,就会弹出一个菜单;再比如,我们要设计一个自动部署的功能,就像eclipse开发时,只要修改了文件,eclipse就会自动将修改的文件部署到服务器中。这两个功能有一个相似的地方,那就是一个对象要时刻监听着另一个对象,只要它的状态一发生改变,自己随之要做出相应的行动。其实,能够实现这一点的方案很多,但是,无疑使用观察者模式是一个主流的选择。

观察者模式的结构

在最基础的观察者模式中,包括以下四个角色:

  • 被观察者:从类图中可以看到,类中有一个用来存放观察者对象的Vector容器(之所以使用Vector而不使用List,是因为多线程操作时,Vector在是安全的,而List则是不安全的),这个Vector容器是被观察者类的核心,另外还有三个方法:attach方法是向这个容器中添加观察者对象;detach方法是从容器中移除观察者对象;notify方法是依次调用观察者对象的对应方法。这个角色可以是接口,也可以是抽象类或者具体的类,因为很多情况下会与其他的模式混用,所以使用抽象类的情况比较多。
  • 观察者:观察者角色一般是一个接口,它只有一个update方法,在被观察者状态发生变化时,这个方法就会被触发调用。
  • 具体的被观察者:使用这个角色是为了便于扩展,可以在此角色中定义具体的业务逻辑。
  • 具体的观察者:观察者接口的具体实现,在这个角色中,将定义被观察者对象状态发生变化时所要处理的逻辑。

观察者模式代码实现

abstract class Subject {  
    private Vector obs = new Vector();  
      
    public void addObserver(Observer obs){  
        this.obs.add(obs);  
    }  
    public void delObserver(Observer obs){  
        this.obs.remove(obs);  
    }  
    protected void notifyObserver(){  
        for(Observer o: obs){  
            o.update();  
        }  
    }  
    public abstract void doSomething();  
}  
  
class ConcreteSubject extends Subject {  
    public void doSomething(){  
        System.out.println("被观察者事件反生");  
        this.notifyObserver();  
    }  
}  
interface Observer {  
    public void update();  
}  
class ConcreteObserver1 implements Observer {  
    public void update() {  
        System.out.println("观察者1收到信息,并进行处理。");  
    }  
}  
class ConcreteObserver2 implements Observer {  
    public void update() {  
        System.out.println("观察者2收到信息,并进行处理。");  
    }  
}  
  
public class Client {  
    public static void main(String[] args){  
        Subject sub = new ConcreteSubject();  
        sub.addObserver(new ConcreteObserver1()); //添加观察者1  
        sub.addObserver(new ConcreteObserver2()); //添加观察者2  
        sub.doSomething();  
    }  
}

运行结果

被观察者事件反生

观察者1收到信息,并进行处理。

观察者2收到信息,并进行处理。

        通过运行结果可以看到,我们只调用了Subject的方法,但同时两个观察者的相关方法都被同时调用了。仔细看一下代码,其实很简单,无非就是在Subject类中关联一下Observer类,并且在doSomething方法中遍历一下Observer的update方法就行了。

观察者模式的优点

观察者与被观察者之间是属于轻度的关联关系,并且是抽象耦合的,这样,对于两者来说都比较容易进行扩展。

观察者模式是一种常用的触发机制,它形成一条触发链,依次对各个观察者的方法进行处理。但同时,这也算是观察者模式一个缺点,由于是链式触发,当观察者比较多的时候,性能问题是比较令人担忧的。并且,在链式结构中,比较容易出现循环引用的错误,造成系统假死。

总结

java语言中,有一个接口Observer,以及它的实现类Observable,对观察者角色常进行了实现。我们可以在jdk的api文档具体查看这两个类的使用方法。

做过VC++、javascript DOM或者AWT开发的朋友都对它们的事件处理感到神奇,了解了观察者模式,就对事件处理机制的原理有了一定的了解了。如果要设计一个事件触发处理机制的功能,使用观察者模式是一个不错的选择,AWT中的事件处理DEM(委派事件模型Delegation Event Model)就是使用观察者模式实现的。

 

23种设计模式(7):中介者模式

定义:用一个中介者对象封装一系列的对象交互,中介者使各对象不需要显示地相互作用,从而使耦合松散,而且可以独立地改变它们之间的交互。

类型:行为类模式

类图:

 

中介者模式的结构

中介者模式又称为调停者模式,从类图中看,共分为3部分:

  •  抽象中介者:定义好同事类对象到中介者对象的接口,用于各个同事类之间的通信。一般包括一个或几个抽象的事件方法,并由子类去实现。
  • 中介者实现类:从抽象中介者继承而来,实现抽象中介者中定义的事件方法。从一个同事类接收消息,然后通过消息影响其他同时类。
  • 同事类:如果一个对象会影响其他的对象,同时也会被其他对象影响,那么这两个对象称为同事类。在类图中,同事类只有一个,这其实是现实的省略,在实际应用中,同事类一般由多个组成,他们之间相互影响,相互依赖。同事类越多,关系越复杂。并且,同事类也可以表现为继承了同一个抽象类的一组实现组成。在中介者模式中,同事类之间必须通过中介者才能进行消息传递。

为什么要使用中介者模式

一般来说,同事类之间的关系是比较复杂的,多个同事类之间互相关联时,他们之间的关系会呈现为复杂的网状结构,这是一种过度耦合的架构,即不利于类的复用,也不稳定。例如在下图中,有六个同事类对象,假如对象1发生变化,那么将会有4个对象受到影响。如果对象2发生变化,那么将会有5个对象受到影响。也就是说,同事类之间直接关联的设计是不好的。

 如果引入中介者模式,那么同事类之间的关系将变为星型结构,从图中可以看到,任何一个类的变动,只会影响的类本身,以及中介者,这样就减小了系统的耦合。一个好的设计,必定不会把所有的对象关系处理逻辑封装在本类中,而是使用一个专门的类来管理那些不属于自己的行为。

我们使用一个例子来说明一下什么是同事类:有两个类A和B,类中各有一个数字,并且要保证类B中的数字永远是类A中数字的100倍。也就是说,当修改类A的数时,将这个数字乘以100赋给类B,而修改类B时,要将数除以100赋给类A。类A类B互相影响,就称为同事类。代码如下:

abstract class AbstractColleague {
    protected int number;

    public int getNumber() {
        return number;
    }

    public void setNumber(int number){
        this.number = number;
    }
    //抽象方法,修改数字时同时修改关联对象
    public abstract void setNumber(int number, AbstractColleague coll);
}

class ColleagueA extends AbstractColleague{
    public void setNumber(int number, AbstractColleague coll) {
        this.number = number;
        coll.setNumber(number*100);
    }
}

class ColleagueB extends AbstractColleague{
    
    public void setNumber(int number, AbstractColleague coll) {
        this.number = number;
        coll.setNumber(number/100);
    }
}

public class Client {
    public static void main(String[] args){

        AbstractColleague collA = new ColleagueA();
        AbstractColleague collB = new ColleagueB();
        
        System.out.println("==========设置A影响B==========");
        collA.setNumber(1288, collB);
        System.out.println("collA的number值:"+collA.getNumber());
        System.out.println("collB的number值:"+collB.getNumber());

        System.out.println("==========设置B影响A==========");
        collB.setNumber(87635, collA);
        System.out.println("collB的number值:"+collB.getNumber());
        System.out.println("collA的number值:"+collA.getNumber());
    }
}

上面的代码中,类A类B通过直接的关联发生关系,假如我们要使用中介者模式,类A类B之间则不可以直接关联,他们之间必须要通过一个中介者来达到关联的目的。

abstract class AbstractColleague {
    protected int number;

    public int getNumber() {
        return number;
    }

    public void setNumber(int number){
        this.number = number;
    }
    //注意这里的参数不再是同事类,而是一个中介者
    public abstract void setNumber(int number, AbstractMediator am);
}

class ColleagueA extends AbstractColleague{

    public void setNumber(int number, AbstractMediator am) {
        this.number = number;
        am.AaffectB();
    }
}

class ColleagueB extends AbstractColleague{

    @Override
    public void setNumber(int number, AbstractMediator am) {
        this.number = number;
        am.BaffectA();
    }
}

abstract class AbstractMediator {
    protected AbstractColleague A;
    protected AbstractColleague B;
    
    public AbstractMediator(AbstractColleague a, AbstractColleague b) {
        A = a;
        B = b;
    }

    public abstract void AaffectB();
    
    public abstract void BaffectA();

}
class Mediator extends AbstractMediator {

    public Mediator(AbstractColleague a, AbstractColleague b) {
        super(a, b);
    }

    //处理A对B的影响
    public void AaffectB() {
        int number = A.getNumber();
        B.setNumber(number*100);
    }

    //处理B对A的影响
    public void BaffectA() {
        int number = B.getNumber();
        A.setNumber(number/100);
    }
}

public class Client {
    public static void main(String[] args){
        AbstractColleague collA = new ColleagueA();
        AbstractColleague collB = new ColleagueB();
        
        AbstractMediator am = new Mediator(collA, collB);
        
        System.out.println("==========通过设置A影响B==========");
        collA.setNumber(1000, am);
        System.out.println("collA的number值为:"+collA.getNumber());
        System.out.println("collB的number值为A的10倍:"+collB.getNumber());

        System.out.println("==========通过设置B影响A==========");
        collB.setNumber(1000, am);
        System.out.println("collB的number值为:"+collB.getNumber());
        System.out.println("collA的number值为B的0.1倍:"+collA.getNumber());
        
    }
}

虽然代码比较长,但是还是比较容易理解的,其实就是把原来处理对象关系的代码重新封装到一个中介类中,通过这个中介类来处理对象间的关系。

中介者模式的优点

  • 适当地使用中介者模式可以避免同事类之间的过度耦合,使得各同事类之间可以相对独立地使用。
  • 使用中介者模式可以将对象间一对多的关联转变为一对一的关联,使对象间的关系易于理解和维护。
  • 使用中介者模式可以将对象的行为和协作进行抽象,能够比较灵活的处理对象间的相互作用。

适用场景

在面向对象编程中,一个类必然会与其他的类发生依赖关系,完全独立的类是没有意义的。一个类同时依赖多个类的情况也相当普遍,既然存在这样的情况,说明,一对多的依赖关系有它的合理性,适当的使用中介者模式可以使原本凌乱的对象关系清晰,但是如果滥用,则可能会带来反的效果。一般来说,只有对于那种同事类之间是网状结构的关系,才会考虑使用中介者模式。可以将网状结构变为星状结构,使同事类之间的关系变的清晰一些。

中介者模式是一种比较常用的模式,也是一种比较容易被滥用的模式。对于大多数的情况,同事类之间的关系不会复杂到混乱不堪的网状结构,因此,大多数情况下,将对象间的依赖关系封装的同事类内部就可以的,没有必要非引入中介者模式。滥用中介者模式,只会让事情变的更复杂。

23种设计模式(6):模版方法模式

定义:定义一个操作中算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变算法的结构即可重定义该算法中的某些特定步骤。

类型:行为类模式

类图:

 

事实上,模版方法是编程中一个经常用到的模式。先来看一个例子,某日,程序员A拿到一个任务:给定一个整数数组,把数组中的数由小到大排序,然后把排序之后的结果打印出来。经过分析之后,这个任务大体上可分为两部分,排序和打印,打印功能好实现,排序就有点麻烦了。但是A有办法,先把打印功能完成,排序功能另找人做。

abstract class AbstractSort {
	
	/**
	 * 将数组array由小到大排序
	 * @param array
	 */
	protected abstract void sort(int[] array);
	
	public void showSortResult(int[] array){
		this.sort(array);
		System.out.print("排序结果:");
		for (int i = 0; i < array.length; i++){
			System.out.printf("%3s", array[i]);
		}
	}
}

写完后,A找到刚毕业入职不久的同事B说:有个任务,主要逻辑我已经写好了,你把剩下的逻辑实现一下吧。于是把AbstractSort类给B,让B写实现。B拿过来一看,太简单了,10分钟搞定,代码如下:

class ConcreteSort extends AbstractSort {

	@Override
	protected void sort(int[] array){
		for(int i=0; i<array.length-1; i++){
			selectSort(array, i);
		}
	}
	
	private void selectSort(int[] array, int index) {
		int MinValue = 32767; // 最小值变量
		int indexMin = 0; // 最小值索引变量
		int Temp; // 暂存变量
		for (int i = index; i < array.length; i++) {
			if (array[i] < MinValue){ // 找到最小值
				MinValue = array[i]; // 储存最小值
				indexMin = i; 
			}
		}
		Temp = array[index]; // 交换两数值
		array[index] = array[indexMin];
		array[indexMin] = Temp;
	}
}

写好后交给A,A拿来一运行:

public class Client {
	public static int[] a = { 10, 32, 1, 9, 5, 7, 12, 0, 4, 3 }; // 预设数据数组
	public static void main(String[] args){
		AbstractSort s = new ConcreteSort();
		s.showSortResult(a);
	}
}

运行结果:

排序结果:  0  1  3  4  5  7  9 10 12 32

运行正常。行了,任务完成。没错,这就是模版方法模式。大部分刚步入职场的毕业生应该都有类似B的经历。一个复杂的任务,由公司中的牛人们将主要的逻辑写好,然后把那些看上去比较简单的方法写成抽象的,交给其他的同事去开发。这种分工方式在编程人员水平层次比较明显的公司中经常用到。比如一个项目组,有架构师,高级工程师,初级工程师,则一般由架构师使用大量的接口、抽象类将整个系统的逻辑串起来,实现的编码则根据难度的不同分别交给高级工程师和初级工程师来完成。怎么样,是不是用到过模版方法模式?

模版方法模式的结构

模版方法模式由一个抽象类和一个(或一组)实现类通过继承结构组成,抽象类中的方法分为三种:

  • 抽象方法:父类中只声明但不加以实现,而是定义好规范,然后由它的子类去实现。
  • 模版方法:由抽象类声明并加以实现。一般来说,模版方法调用抽象方法来完成主要的逻辑功能,并且,模版方法大多会定义为final类型,指明主要的逻辑功能在子类中不能被重写。
  • 钩子方法:由抽象类声明并加以实现。但是子类可以去扩展,子类可以通过扩展钩子方法来影响模版方法的逻辑。
  • 抽象类的任务是搭建逻辑的框架,通常由经验丰富的人员编写,因为抽象类的好坏直接决定了程序是否稳定性。

实现类用来实现细节。抽象类中的模版方法正是通过实现类扩展的方法来完成业务逻辑。只要实现类中的扩展方法通过了单元测试,在模版方法正确的前提下,整体功能一般不会出现大的错误。

模版方法的优点及适用场景

容易扩展。一般来说,抽象类中的模版方法是不易反生改变的部分,而抽象方法是容易反生变化的部分,因此通过增加实现类一般可以很容易实现功能的扩展,符合开闭原则。

便于维护。对于模版方法模式来说,正是由于他们的主要逻辑相同,才使用了模版方法,假如不使用模版方法,任由这些相同的代码散乱的分布在不同的类中,维护起来是非常不方便的。

比较灵活。因为有钩子方法,因此,子类的实现也可以影响父类中主逻辑的运行。但是,在灵活的同时,由于子类影响到了父类,违反了里氏替换原则,也会给程序带来风险。这就对抽象类的设计有了更高的要求。

在多个子类拥有相同的方法,并且这些方法逻辑相同时,可以考虑使用模版方法模式。在程序的主框架相同,细节不同的场合下,也比较适合使用这种模式。

23种设计模式(5):原型模式

定义:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。

类型:创建类模式

类图:

 

原型模式主要用于对象的复制,它的核心是就是类图中的原型类Prototype。Prototype类需要具备以下两个条件:

  • 实现Cloneable接口。在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。
  • 重写Object类中的clone方法。Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此,Prototype类需要将clone方法的作用域修改为public类型。

原型模式是一种比较简单的模式,也非常容易理解,实现一个接口,重写一个方法即完成了原型模式。在实际应用中,原型模式很少单独出现。经常与其他模式混用,他的原型类Prototype也常用抽象类来替代。

实现代码:

class Prototype implements Cloneable {
    public Prototype clone(){
        Prototype prototype = null;
        try{
            prototype = (Prototype)super.clone();
        }catch(CloneNotSupportedException e){
            e.printStackTrace();
        }
        return prototype; 
	}
}

class ConcretePrototype extends Prototype{
    public void show(){
        System.out.println("原型模式实现类");
    }
}

public class Client {
    public static void main(String[] args){
        ConcretePrototype cp = new ConcretePrototype();
        for(int i=0; i< 10; i++){
           ConcretePrototype clonecp = (ConcretePrototype)cp.clone();
           clonecp.show();
        }
    }
}

原型模式的优点及适用场景

使用原型模式创建对象比直接new一个对象在性能上要好的多,因为Object类的clone方法是一个本地方法,它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。

使用原型模式的另一个好处是简化对象的创建,使得创建对象就像我们在编辑文档时的复制粘贴一样简单。

因为以上优点,所以在需要重复地创建相似对象时可以考虑使用原型模式。比如需要在一个循环体内创建对象,假如对象创建过程比较复杂或者循环次数很多的话,使用原型模式不但可以简化创建过程,而且可以使系统的整体性能提高很多。

原型模式的注意事项

    • 使用原型模式复制对象不会调用类的构造方法。因为对象的复制是通过调用Object类的clone方法来完成的,它直接在内存中复制数据,因此不会调用到类的构造方法。不但构造方法中的代码不会执行,甚至连访问权限都对原型模式无效。还记得单例模式吗?单例模式中,只要将构造方法的访问权限设置为private型,就可以实现单例。但是clone方法直接无视构造方法的权限,所以,单例模式与原型模式是冲突的,在使用时要特别注意。
    • 深拷贝与浅拷贝。Object类的clone方法只会拷贝对象中的基本的数据类型,对于数组、容器对象、引用对象等都不会拷贝,这就是浅拷贝。如果要实现深拷贝,必须将原型模式中的数组、容器对象、引用对象等另行拷贝。例如:
public class Prototype implements Cloneable {
	private ArrayList list = new ArrayList();
	public Prototype clone(){
		Prototype prototype = null;
		try{
			prototype = (Prototype)super.clone();
			prototype.list = (ArrayList) this.list.clone();
		}catch(CloneNotSupportedException e){
			e.printStackTrace();
		}
		return prototype; 
	}
}

由于ArrayList不是基本类型,所以成员变量list,不会被拷贝,需要我们自己实现深拷贝,幸运的是java提供的大部分的容器类都实现了Cloneable接口。所以实现深拷贝并不是特别困难。

PS:深拷贝与浅拷贝问题中,会发生深拷贝的有java中的8种基本类型以及他们的封装类型,另外还有String类型。其余的都是浅拷贝。

23种设计模式(4):建造者模式

定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

类型:创建类模式

类图:

 

四个要素

  • 产品类:一般是一个较为复杂的对象,也就是说创建对象的过程比较复杂,一般会有比较多的代码量。在本类图中,产品类是一个具体的类,而非抽象类。实际编程中,产品类可以是由一个抽象类与它的不同实现组成,也可以是由多个抽象类与他们的实现组成。
  • 抽象建造者:引入抽象建造者的目的,是为了将建造的具体过程交与它的子类来实现。这样更容易扩展。一般至少会有两个抽象方法,一个用来建造产品,一个是用来返回产品。
  • 建造者:实现抽象类的所有未实现的方法,具体来说一般是两项任务:组建产品;返回组建好的产品。
  • 导演类:负责调用适当的建造者来组建产品,导演类一般不与产品类发生依赖关系,与导演类直接交互的是建造者类。一般来说,导演类被用来封装程序中易变的部分。

代码实现

class Product {
	private String name;
	private String type;
	public void showProduct(){
		System.out.println("名称:"+name);
		System.out.println("型号:"+type);
	}
	public void setName(String name) {
		this.name = name;
	}
	public void setType(String type) {
		this.type = type;
	}
}

abstract class Builder {
	public abstract void setPart(String arg1, String arg2);
	public abstract Product getProduct();
}
class ConcreteBuilder extends Builder {
	private Product product = new Product();
	
	public Product getProduct() {
		return product;
	}

	public void setPart(String arg1, String arg2) {
		product.setName(arg1);
		product.setType(arg2);
	}
}

public class Director {
	private Builder builder = new ConcreteBuilder();
	public Product getAProduct(){
		builder.setPart("宝马汽车","X7");
		return builder.getProduct();
	}
	public Product getBProduct(){
		builder.setPart("奥迪汽车","Q5");
		return builder.getProduct();
	}
}
public class Client {
	public static void main(String[] args){
		Director director = new Director();
		Product product1 = director.getAProduct();
		product1.showProduct();

		Product product2 = director.getBProduct();
		product2.showProduct();
	}
}

建造者模式的优点

首先,建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在导演类中对整体而言可以取得比较好的稳定性。

其次,建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。

建造者模式与工厂模式的区别

我们可以看到,建造者模式与工厂模式是极为相似的,总体上,建造者模式仅仅只比工厂模式多了一个“导演类”的角色。在建造者模式的类图中,假如把这个导演类看做是最终调用的客户端,那么图中剩余的部分就可以看作是一个简单的工厂模式了。

与工厂模式相比,建造者模式一般用来创建更为复杂的对象,因为对象的创建过程更为复杂,因此将对象的创建过程独立出来组成一个新的类——导演类。也就是说,工厂模式是将对象的全部创建过程封装在工厂类中,由工厂类向客户端提供最终的产品;而建造者模式中,建造者类一般只提供产品类中各个组件的建造,而将具体建造过程交付给导演类。由导演类负责将各个组件按照特定的规则组建为产品,然后将组建好的产品交付给客户端。

总结

建造者模式与工厂模式类似,他们都是建造者模式,适用的场景也很相似。一般来说,如果产品的建造很复杂,那么请用工厂模式;如果产品的建造更复杂,那么请用建造者模式。

23种设计模式(3):抽象工厂模式

定义:为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类。

类型:创建类模式

类图:

抽象工厂模式与工厂方法模式的区别

抽象工厂模式是工厂方法模式的升级版本,他用来创建一组相关或者相互依赖的对象。他与工厂方法模式的区别就在于,工厂方法模式针对的是一个产品等级结构;而抽象工厂模式则是针对的多个产品等级结构。在编程中,通常一个产品结构,表现为一个接口或者抽象类,也就是说,工厂方法模式提供的所有产品都是衍生自同一个接口或抽象类,而抽象工厂模式所提供的产品则是衍生自不同的接口或抽象类。

在抽象工厂模式中,有一个产品族的概念:所谓的产品族,是指位于不同产品等级结构中功能相关联的产品组成的家族。抽象工厂模式所提供的一系列产品就组成一个产品族;而工厂方法提供的一系列产品称为一个等级结构。我们依然拿生产汽车的例子来说明他们之间的区别。

 在上面的类图中,两厢车和三厢车称为两个不同的等级结构;而2.0排量车和2.4排量车则称为两个不同的产品族。再具体一点,2.0排量两厢车和2.4排量两厢车属于同一个等级结构,2.0排量三厢车和2.4排量三厢车属于另一个等级结构;而2.0排量两厢车和2.0排量三厢车属于同一个产品族,2.4排量两厢车和2.4排量三厢车属于另一个产品族。

明白了等级结构和产品族的概念,就理解工厂方法模式和抽象工厂模式的区别了,如果工厂的产品全部属于同一个等级结构,则属于工厂方法模式;如果工厂的产品来自多个等级结构,则属于抽象工厂模式。在本例中,如果一个工厂模式提供2.0排量两厢车和2.4排量两厢车,那么他属于工厂方法模式;如果一个工厂模式是提供2.4排量两厢车和2.4排量三厢车两个产品,那么这个工厂模式就是抽象工厂模式,因为他提供的产品是分属两个不同的等级结构。当然,如果一个工厂提供全部四种车型的产品,因为产品分属两个等级结构,他当然也属于抽象工厂模式了。

抽象工厂模式代码

interface IProduct1 {
	public void show();
}
interface IProduct2 {
	public void show();
}

class Product1 implements IProduct1 {
	public void show() {
		System.out.println("这是1型产品");
	}
}
class Product2 implements IProduct2 {
	public void show() {
		System.out.println("这是2型产品");
	}
}

interface IFactory {
	public IProduct1 createProduct1();
	public IProduct2 createProduct2();
}
class Factory implements IFactory{
	public IProduct1 createProduct1() {
		return new Product1();
	}
	public IProduct2 createProduct2() {
		return new Product2();
	}
}

public class Client {
	public static void main(String[] args){
		IFactory factory = new Factory();
		factory.createProduct1().show();
		factory.createProduct2().show();
	}
}

抽象工厂模式的优点

抽象工厂模式除了具有工厂方法模式的优点外,最主要的优点就是可以在类的内部对产品族进行约束。所谓的产品族,一般或多或少的都存在一定的关联,抽象工厂模式就可以在类内部对产品族的关联关系进行定义和描述,而不必专门引入一个新的类来进行管理。

抽象工厂模式的缺点

产品族的扩展将是一件十分费力的事情,假如产品族中需要增加一个新的产品,则几乎所有的工厂类都需要进行修改。所以使用抽象工厂模式时,对产品等级结构的划分是非常重要的。

适用场景

当需要创建的对象是一系列相互关联或相互依赖的产品族时,便可以使用抽象工厂模式。说的更明白一点,就是一个继承体系中,如果存在着多个等级结构(即存在着多个抽象类),并且分属各个等级结构中的实现类之间存在着一定的关联或者约束,就可以使用抽象工厂模式。假如各个等级结构中的实现类之间不存在关联或约束,则使用多个独立的工厂来对产品进行创建,则更合适一点。

总结

无论是简单工厂模式,工厂方法模式,还是抽象工厂模式,他们都属于工厂模式,在形式和特点上也是极为相似的,他们的最终目的都是为了解耦。在使用时,我们不必去在意这个模式到底工厂方法模式还是抽象工厂模式,因为他们之间的演变常常是令人琢磨不透的。经常你会发现,明明使用的工厂方法模式,当新需求来临,稍加修改,加入了一个新方法后,由于类中的产品构成了不同等级结构中的产品族,它就变成抽象工厂模式了;而对于抽象工厂模式,当减少一个方法使的提供的产品不再构成产品族之后,它就演变成了工厂方法模式。

所以,在使用工厂模式时,只需要关心降低耦合度的目的是否达到了。

个人理解
抽象工厂模式和工厂模式的区别是在于不同的维度。抽象工厂是多维的,举个例子,汽车分为小轿车,越野车,火车这几种类型,不同的类型下又有不同的规格,比如以小轿车来说:2..0排量的小驾车和2.4排量的小车,这是不同的规格。所以在设计抽象工厂接口时,抽象工厂所抽象是轿车的类型,抽象工厂的实现是提供生产多种轿车类型的实例。这个实例再去提供生产不同规格轿车的实例。而工厂模式是单维对的,只提供一种类型多种规格的轿车。

23种设计模式(2):工厂方法模式

定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂方法使一个类的实例化延迟到其子类。

类型:创建类模式

类图:

 

工厂方法模式代码

interface IProduct {
  public void productMethod();
}

class Product implements IProduct {
  public void productMethod() {
  System.out.println("产品");
 }
}

interface IFactory {
  public IProduct createProduct();
}

class Factory implements IFactory {
  public IProduct createProduct() {
  return new Product();
 }
}

public class Client {
  public static void main(String[] args) {
  IFactory factory = new Factory();
  IProduct prodect = factory.createProduct();
  prodect.productMethod();
 }
}

工厂模式:

首先需要说一下工厂模式。工厂模式根据抽象程度的不同分为三种:简单工厂模式(也叫静态工厂模式)、本文所讲述的工厂方法模式、以及抽象工厂模式。工厂模式是编程中经常用到的一种模式。它的主要优点有:

  • 可以使代码结构清晰,有效地封装变化。在编程中,产品类的实例化有时候是比较复杂和多变的,通过工厂模式,将产品的实例化封装起来,使得调用者根本无需关心产品的实例化过程,只需依赖工厂即可得到自己想要的产品。
  • 对调用者屏蔽具体的产品类。如果使用工厂模式,调用者只关心产品的接口就可以了,至于具体的实现,调用者根本无需关心。即使变更了具体的实现,对调用者来说没有任何影响。
  • 降低耦合度。产品类的实例化通常来说是很复杂的,它需要依赖很多的类,而这些类对于调用者来说根本无需知道,如果使用了工厂方法,我们需要做的仅仅是实例化好产品类,然后交给调用者使用。对调用者来说,产品所依赖的类都是透明的。

 

工厂方法模式:

       通过工厂方法模式的类图可以看到,工厂方法模式有四个要素:

  • 工厂接口。工厂接口是工厂方法模式的核心,与调用者直接交互用来提供产品。在实际编程中,有时候也会使用一个抽象类来作为与调用者交互的接口,其本质上是一样的。
  • 工厂实现。在编程中,工厂实现决定如何实例化产品,是实现扩展的途径,需要有多少种产品,就需要有多少个具体的工厂实现。
  • 产品接口。产品接口的主要目的是定义产品的规范,所有的产品实现都必须遵循产品接口定义的规范。产品接口是调用者最为关心的,产品接口定义的优劣直接决定了调用者代码的稳定性。同样,产品接口也可以用抽象类来代替,但要注意最好不要违反里氏替换原则。
  • 产品实现。实现产品接口的具体类,决定了产品在客户端中的具体行为。

前文提到的简单工厂模式跟工厂方法模式极为相似,区别是:简单工厂只有三个要素,他没有工厂接口,并且得到产品的方法一般是静态的。因为没有工厂接口,所以在工厂实现的扩展性方面稍弱,可以算所工厂方法模式的简化版,关于简单工厂模式,在此一笔带过。

 

适用场景:

不管是简单工厂模式,工厂方法模式还是抽象工厂模式,他们具有类似的特性,所以他们的适用场景也是类似的。

首先,作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过new就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。

其次,工厂模式是一种典型的解耦模式,迪米特法则在工厂模式中表现的尤为明显。假如调用者自己组装产品需要增加依赖关系时,可以考虑使用工厂模式。将会大大降低对象之间的耦合度。

再次,由于工厂模式是依靠抽象架构的,它把实例化产品的任务交由实现类完成,扩展性比较好。也就是说,当需要系统有比较好的扩展性时,可以考虑工厂模式,不同的产品用不同的实现工厂来组装。

典型应用

要说明工厂模式的优点,可能没有比组装汽车更合适的例子了。场景是这样的:汽车由发动机、轮、底盘组成,现在需要组装一辆车交给调用者。假如不使用工厂模式,代码如下:

class Engine {
  public void getStyle(){
  System.out.println("这是汽车的发动机");
 }
}
class Underpan {
  public void getStyle(){
  System.out.println("这是汽车的底盘");
 }
}
class Wheel {
  public void getStyle(){
  System.out.println("这是汽车的轮胎");
 }
}
public class Client {
  public static void main(String[] args) {
  Engine engine = new Engine();
  Underpan underpan = new Underpan();
  Wheel wheel = new Wheel();
  ICar car = new Car(underpan, wheel, engine);
  car.show();
 }
}

可以看到,调用者为了组装汽车还需要另外实例化发动机、底盘和轮胎,而这些汽车的组件是与调用者无关的,严重违反了迪米特法则,耦合度太高。并且非常不利于扩展。另外,本例中发动机、底盘和轮胎还是比较具体的,在实际应用中,可能这些产品的组件也都是抽象的,调用者根本不知道怎样组装产品。假如使用工厂方法的话,整个架构就显得清晰了许多。

interface IFactory {
	public ICar createCar();
}
class Factory implements IFactory {
	public ICar createCar() {
		Engine engine = new Engine();
		Underpan underpan = new Underpan();
		Wheel wheel = new Wheel();
		ICar car = new Car(underpan, wheel, engine);
		return car;
	}
}
public class Client {
	public static void main(String[] args) {
		IFactory factory = new Factory();
		ICar car = factory.createCar();
		car.show();
	}
}

使用工厂方法后,调用端的耦合度大大降低了。并且对于工厂来说,是可以扩展的,以后如果想组装其他的汽车,只需要再增加一个工厂类的实现就可以。无论是灵活性还是稳定性都得到了极大的提高。

23种设计模式(1):单例模式

定义:确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

类型:创建类模式

类图:

类图知识点:

1.类图分为三部分,依次是类名、属性、方法

2.以<<开头和以>>结尾的为注释信息

3.修饰符+代表public,-代表private,#代表protected,什么都没有代表包可见。

4.带下划线的属性或方法代表是静态的。

5.对类图中对象的关系不熟悉的朋友可以参考文章:设计模式中类的关系

单例模式应该是23种设计模式中最简单的一种模式了。它有以下几个要素:

  • 私有的构造方法
  • 指向自己实例的私有静态引用
  • 以自己实例为返回值的静态的公有的方法

单例模式根据实例化对象时机的不同分为两种:一种是饿汉式单例,一种是懒汉式单例。饿汉式单例在单例类被加载时候,就实例化一个对象交给自己的引用;而懒汉式在调用取得实例方法的时候才会实例化对象。代码如下:

饿汉式单例

public class Singleton {    
   private static Singleton singleton = new Singleton();    
   private Singleton(){}    
   public static  Singleton getInstance(){
     return singleton;
   }
}

懒汉式单例

public class Singleton {
     private static Singleton singleton;
     private Singleton(){}
     public static synchronized Singleton getInstance(){
        if(singleton==null){
          singleton = new Singleton();
          }
     return singleton;
     }
}

单例模式的优点:

  • 在内存中只有一个对象,节省内存空间。
  • 避免频繁的创建销毁对象,可以提高性能。
  • 避免对共享资源的多重占用。
  • 可以全局访问。

适用场景:由于单例模式的以上优点,所以是编程中用的比较多的一种设计模式。我总结了一下我所知道的适合使用单例模式的场景:

  • 需要频繁实例化然后销毁的对象。
  • 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
  • 有状态的工具类对象。
  • 频繁访问数据库或文件的对象。
  • 以及其他我没用过的所有要求只有一个对象的场景。

单例模式注意事项:

  • 只能使用单例类提供的方法得到单例对象,不要使用反射,否则将会实例化一个新对象。
  • 不要做断开单例类对象与类中静态引用的危险操作。
  • 多线程使用单例使用共享资源时,注意线程安全问题。

关于java中单例模式的一些争议:

单例模式的对象长时间不用会被jvm垃圾收集器收集吗

看到不少资料中说:如果一个单例对象在内存中长久不用,会被jvm认为是一个垃圾,在执行垃圾收集的时候会被清理掉。对此这个说法,笔者持怀疑态度,笔者本人的观点是:在hotspot虚拟机1.6版本中,除非人为地断开单例中静态引用到单例对象的联接,否则jvm垃圾收集器是不会回收单例对象的。

对于这个争议,笔者单独写了一篇文章进行讨论,如果您有不同的观点或者有过这方面的经历请进入文章单例模式讨论篇:单例模式与垃圾收集参与讨论。

在一个jvm中会出现多个单例吗

在分布式系统、多个类加载器、以及序列化的的情况下,会产生多个单例,这一点是无庸置疑的。那么在同一个jvm中,会不会产生单例呢?使用单例提供的getInstance()方法只能得到同一个单例,除非是使用反射方式,将会得到新的单例。代码如下

Class c = Class.forName(Singleton.class.getName());
Constructor ct = c.getDeclaredConstructor();
ct.setAccessible(true);
Singleton singleton = (Singleton)ct.newInstance();

这样,每次运行都会产生新的单例对象。所以运用单例模式时,一定注意不要使用反射产生新的单例对象。

懒汉式单例线程安全吗

主要是网上的一些说法,懒汉式的单例模式是线程不安全的,即使是在实例化对象的方法上加synchronized关键字,也依然是危险的,但是笔者经过编码测试,发现加synchronized关键字修饰后,虽然对性能有部分影响,但是却是线程安全的,并不会产生实例化多个对象的情况。

单例模式只有饿汉式和懒汉式两种吗

饿汉式单例和懒汉式单例只是两种比较主流和常用的单例模式方法,从理论上讲,任何可以实现一个类只有一个实例的设计模式,都可以称为单例模式。

单例类可以被继承吗

饿汉式单例和懒汉式单例由于构造方法是private的,所以他们都是不可继承的,但是其他很多单例模式是可以继承的,例如登记式单例。

饿汉式单例好还是懒汉式单例好

在java中,饿汉式单例要优于懒汉式单例。C++中则一般使用懒汉式单例。

单例模式比较简单,在此就不举例代码演示了。