设计模式总结(四)

Always on the way of programming.

作者: Ian | 2018-03-08 | 阅读



右侧可以看目录,点击直接跳转,这个系列一共写了4篇(可以点击下面直接跳转然后看右侧目录):

单例模式

单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

例子:

public calss Singleton{
	private static Singleton instance;
	
	// 构造方法私有化,这就堵死了外界利用new创建此类实例可能。
	private singleton(){}
	
	// 此方法是获得本类实例的唯一全局访问点
	public static Singleton getInstance(){
		if (null == instance){
			instance = new Singleton();
		}
		return instance;
	}
}
public class main{
	public static void main(String[] args){
		Singleton s1 = new Singleton.getInstance();
		Singleton s2 = new Singleton.getInstance();
		
		// 比较两次实例化后对象的结果是实例相同
		if (s1 == s2){
			System.out.println("两个对象是相同的实例.");
		}
	}
} 

饿汉式:

  静态初始化的方式是在自己被加载时就将自己实例化。

懒汉式:

  第一次被引用时,才会将自己实例化,所以叫懒汉式。   

桥接模式

合成/聚合复用原则:尽量使用合成/聚合,尽量不要使用类的继承。

桥接模式:将抽象部分与它的实现部分分离,使它们都可以独立的变化。

合成/聚合复用原则:

  好处:优先使用对象的合成/聚合将有助于你保持每个类被封装,并被集中在单个任务上。这样类和继承层次会保持较小规模,并且不太可能增长为不可控的庞然大物。
  继承的缺点、不足:对象的继承关系是在编译时就定义好了,所以无法在运行时改变从父类继承的实现。子类的实现与父类有非常紧密的依赖关系,以至于父类视线中的任何变化必然会导致子类发生变化。当你需要复用子类时,如果继承下来的实现不适合解决新的问题,则父类必须重写或被其他更适合的类替换。这种依赖关系限制了灵活性并最终限制了复用性。
  总结:在面向对象设计中,我们还有一个很重要的设计原则,那就是合成/聚合复用原则。即优先使用对象合成/聚合,而不是继承。    例子:

public abstract class Implementor{
	public abstract void operation();
}
// 具体的实现
public class ConcreteImplementorA extends Implementor{
	public void operation(){
		System.out.println("具体实现A的方法执行");
	}
}

class ConcreteImplementorB extends Implementor{
	public void operation(){
		System.out.println("具体实现B方法执行");
	}
}
public class Abstraction{
	protected Implementor implementor;
	
	public void setImplementor(Implementor implementor){
		this.implementor = implementor;
	}
	
	public void operation(){
		implementor.operation();
	}
}
// 被提炼抽象
public class RefinedAbstraction extends Aabstraction{
	public void operation(){
		implementor.operation();
	}
}
public class main{
	public static void main(String[] args){
		Absstraction ab = new RefinedAbstraction();
		
		ab.setImplementor(new ConcreteImplementorA());
		ab.operation();
		
		ab.setImplementor(new ConcreteImplementorB());
		ab.operation();
	}
}

应用场景:

  在发现我们需要多角度去分类实现对象,而只用继承会造成大量的类增加,不能满足开放-封闭原则时,就应该考虑使用桥接模式了。   

总结:

  在实现系统可能有多角度分类,每一种分类都有可能变化,那么就把这种多角度分离出来让它们独立变化,减少它们之间的耦合。   

命令模式

命令模式:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。

例子:

// 用来声明执行操作的接口。
public abstract class Command{
	protected Receiver receiver;
	
	public Command(Receiver receiver){
		this.receiver = receiver;
	}
	
	abstract public void execute();
}
// 将一个接受者对象绑定于一个动作,调用接受者相应的操作,以实现exeute。
public class ConcreteCommand extends Command{
	public ConcreteCommand(Receiver receiver){
		super(receiver);
	}
	
	public void execute(){
		receiver.action();
	}
}
// 要求该命令执行这个请求。
public class Invoker{
	private Command command;
	
	public void setCommand(Command command){
		this.command = command;
	}
	
	public void excuteCommand(){
		command.execute();
	}
}
// 知道如何实施与执行一个请求相关的操作,任何类都可能作为一个接受者。
public class Receiver{
	public void action(){
		System.out.println("执行请求!");
	}
}
// 客户端代码,创建一个具体命令对象并设定它的接受者。
public class Main{
	public static void main(String[] args){
		Receiver r = new Receiver();  // 具体实施与执行
		Command c = new ConcreteCommand(r); // 接受者,执行execute()里面的action();
		Invoker i = new Invoker(); // 命令对象
		i.setCommand(c);
		i.executeCommand(); // 执行command里面的execute()---还是执行Receiver的action();
	}
}

优点:

  1. 它能较容易地设计一个命令队列;
  2. 在需要的情况下,可以较容易地将命令记入日志;
  3. 允许接受请求的一方决定是否要否决请求;
  4. 可以容易地实现对请求的撤销和重做;
  5. 由于加进新的具体命令类不影响其他的类,因此增加新的具体命令类很容易。
  6. 命令模式把请求一个操作的对象与知道怎么执行一个操作的对象分隔开。

番外:

  不要为代码添加基于猜测的、实际不需要的功能。如果不清楚一个系统是否需要命令模式,一般就不要着急去实现它,事实上,在需要的时候通过重构实现这个模式并不困难,只有在真正需要如撤销/恢复材质等功能时,把原来的代码重构为命令模式才有意义。   

职责链模式

职责链模式:使多个对象有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这个对象连城一条链,并沿着这条链传递该请求,知道有一个对象处理它为止。

例子:

// 定义一个处理请示的接口
public abstract class Handler{
	protected Handler successor;
	
	//设置继承者
	pulbic void setSuccessor(Handler successor){
		this.successor = successor;
	}
	
	// 处理请求的抽象方法
	public abstract void handleRequest(int request);
	
}
// ConcreteHandler类,处理它所负责的请求,可访问它的后继者,如果可处理该请求,就处理之,否则就将该请求转发给它的后继者。
public class ConcreteHandle1 extends Hadler{
	public void handleRequest(int request){
		// 0 到 10,处理此请求
		if (request >= 0 && request < 10){
          System.out.println("处理 0 ~ 10 请求:" + request);
		}
		else if(null != succesor){
			// 转移到下一位
			successor.handleRequest(request);
		}
	}
}
// ConcreteHandler类,处理它所负责的请求,可访问它的后继者,如果可处理该请求,就处理之,否则就将该请求转发给它的后继者。
public class ConcreteHandle2 extends Hadler{
	public void handleRequest(int request){
		// 10 到 20,处理此请求
		if (request >= 10 && request < 20){
          System.out.println("处理 10 ~ 20 请求:" + request);
		}
		else if(null != succesor){
			// 转移到下一位
			successor.handleRequest(request);
		}
	}
}
// ConcreteHandler类,处理它所负责的请求,可访问它的后继者,如果可处理该请求,就处理之,否则就将该请求转发给它的后继者。
public class ConcreteHandle3 extends Hadler{
	public void handleRequest(int request){
		// 20 到 30,处理此请求
		if (request >= 20 && request < 30){
          System.out.println("处理 20 ~ 30 请求:" + request);
		}
		else if(null != succesor){
			// 转移到下一位
			successor.handleRequest(request);
		}
	}
}
public class main{
	public static void main(String[] args){
		Handler h1 = new ConcreteHandler1();
		Handler h2 = new ConcreteHandler2();
		Handler h3 = new ConcreteHandler3();

      // 设置职责链上家与下家
      h1.setSuccessor(h2);
      h2.setSuccessor(h3);

      // 循环给最小处理者提交请求,不同的数额,由不同的权限处理者处理。
      int[] requests = {2, 5, 14, 22, 18, 3, 27, 20};
      for (int request : requests) {
        h1.handleRequest(request);
      }
	}
}

  重点:当客户提交一个请求时,请求是沿链传递直到有一个ConcreteHandler对象负责处理它。

优点:

  接收者和发送者都没有对方的明确信息,且链中的对象自己也并不知道链的结构。结果是职责链可简化对象的相互连接,它们仅需要保持一个指向其后继者的引用,而不需保持它所有的候选接受者的引用–也就大大降低了耦合度。   

注意:

  一个请求极有可能到了链的末端都得不到处理,或者因为没有正确配置而得不到处理。

中介者模式

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

例子:

// 抽象中介者类
public abstract class Mediator{
	// 定义一个抽象的发送消息方法,使得同事对象和发送信息
	public abstract void send(String message,Colleague colleague);
}
// 抽象同事类
abstract class Colleague{
	protected Mediator mediator;
	
	// 构造方法,得到中介者对象
	public Colleague(Mediator mediator){
		this.mediator = mediator;
	}
}
// 具体中介者类
public ConcreteMediator extends Mediator{
	private ConcreteColleague1 colleague1;
	private ConcreteColleague2 colleague2;
	
	
	public void setColleague1(ConcreteColleague1 concreteColleague1){
		this.colleague1 = concreteColleague1;
	}
	
	public void setColleague2(ConcreteColleague1 concreteColleague2){
		this.colleague2 = concreteColleague2;
	}
	
	// 重写发送信息的方法,根据对象做出选择判断,通知对象
	public void send(String message, Colleague colleague){
		if (colleague == colleague1){
             colleague1.notify(message);
		}else{
             colleague2.notify(message);
		}
	}
}
// ConcreteColleague1 和 ConcreteColeague2等各种同事对象
public class ConcreteColleage1 extends Colleague{
	public ConcreteColleague1(Mediator mediator){
		super(mediator);
	}
	
	public void send(String message){
		mediator.send(message,this);
	}
	
	public void notify(String message){
		System.out.println("同事1得到信息:" + message);
	}
}

class ConcreteColleage2 extends Colleague{
	public ConcreteColleague2(Mediator mediator){
		super(mediator);
	}
	
	public void send(String message){
		mediator.send(message,this);
	}
	
	public void notify(String message){
		System.out.println("同事2得到信息:" + message);
	}
}
public class main{
	public static void main(String[] args){
		ConcreteMediator m = new ConcreteMediator();
		
		// 让两个具体同事类认识中介者对象
		ConcreteColleague1 c1 = new ConcreteColleague1(m);
		ConcreteColleague2 c2 = new ConcreteColleague2(m);
		
		// 让中介者认识各个具体同事类对象
		m. setColleague1(c1);
		m. setColleague2(c2);
		
		// 具体同事类对象的发送信息都是通过中介者转发
		c1.send("吃过饭了么?我请客。");
		c2.send("ok,走起。");
	}
}

总结:

  由于有了Mediator,使得ConcreteColleague1 和 ConcreteColleague2 在发送消息和接收信息时其实是通过中介者来完成的,这就减少了它们之间的耦合度。   

注意: 

  中介者模式很容易在系统中应用,也很容易在系统中误用。当系统出现了“多对多”交互复杂的对象群时,不要急于使用中介者模式,而要先反思你的系统在设计上是不是合理的。   

优点:

  首先,Mediator的出现减少了各个Colleague的耦合,使得可以独立地改变和复用各个Colleague类和Mediator;其次,由于把对象如何协作进行了抽象,将中介作为一个独立的概念并将其封装在一个对象中,这样关注的对象就从对象各自本身的行为转移到它们之间的交互上来,也就是站在一个更宏观的角度去看待系统。   

缺点:

  由于ConcreteMediator控制了集中化,于是就把交互复杂性变为了中介者的复杂性,这就使得中介者会变得比任何一个ConcreteCollague 都复杂。
     中介者模式的优点来自集中控制,其缺点也是它,使用时需考虑清楚。

使用场景:

  中介者模式一般应用于一组对象以定义良好但是复杂的方式进行通信的场合。

访问者模式

访问者模式:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

先上代码:

// visitor 类,为该对象结构中ConcreteElement的每一个类声明一个Visit操作。
public abstract class Visitor{
	public abstract void visitConcreteElementA(ConcreteElementA concreteElementA);
	public abstract void visitConcreteElementB(ConcreteElementB concreteElementB);
}
// ConcreteVisitor1 和 ConcreteVisitor2 类,具体访问者,实现每个由Visitor 声明的操作。每个操作实现算法的一部分,而该算法片段乃是对应于结构中对象的类。

public class ConcreteVisitor1 extends Visitor{
  @Override
  public void visitConcreteElementA(ConcreteElementA concreteElementA) {
    concreteElementA.operatironA();
  }

  @Override
  public void visitConcreteElementB(ConcreteElementB concreteElementB) {
    concreteElementB.operatironB();
  }
}
class ConcreteVisitor2 extends Visitor {

  @Override
  public void visitConcreteElementA(ConcreteElementA concreteElementA) {
    concreteElementA.operatironA();
  }

  @Override
  public void visitConcreteElementB(ConcreteElementB concreteElementB) {
    concreteElementB.operatironB();
  }
}
// 定义一个Accept操作,它以一个访问者为参数。
public abstract class Element{
	public abstract void accept(Visitor visitor);
}
// ConcreteElmentA 和 ConcreteElementB 类,具体元素,实现Accept
public class ConcreteElementA extends Element{
  // 充分利用双分派技术,实现处理与数据结构的分离
  @Override
  public void accept(Visitor visitor) {
    visitor.visitConcreteElementA(this);
  }

  // 其它相关方法
  public void operatironA() {
    System.out.println("ConcreteElmentA");
  }
}

class ConcreteElementB extends Element {

  @Override
  public void accept(Visitor visitor) {
    visitor.visitConcreteElementB(this);
  }

  public void operatironB() {
    System.out.println("ConcreteElmentB");
  }
}
// ObjectStructure 类,能枚举它的元素,可以提供一个高层的接口以允许访问者访问它的元素。
public class ObjectStructure{
	private List<Element> elements = new ArrayList<Element>();
	
	public void attach(Element element){
		elements.add(element);
	}
	
	public void detach(Element element){
		elements.remove(element);
	}
	
	public void accept(Visitor visitor){
		for (Element e : elements){
			e.accept(visitor);
		}
	}
}
public class main{
	public static void main(String[] args){
		ObjectStructure o = new ObjectStructure();
		o.attach(new ConcreteElementA());
		o.attach(new ConcreteElementB());
		
		ConcreteVisitor1 v1 = new ConcreteVisitor1():
		ConcreteVisitor2 v2 = new ConcreteVisitor2():
		
		o.accept(v1);
		o.accept(v2);
	}
}

应用场景:

  访问者模式适用于数据结构相对稳定的系统。   

总结:

  它把数据结构和作用与结构上的操作之间的耦合解脱开,使得操作集合可以相对自由地演化。

目的:

  是要把处理从从数据结构分离出来。– 很多系统可以按照算法和数据结构分开,如果这样的系统有比较稳定的数据结构,又有易于变化的算法的话,使用访问者模式就比较适合的,因为访问者模式使得算法操作的增加变得容易。反之,如果这样的系统的数据结构对象易于变化,经常要有新的数据对象增加进来,就不适合访问者模式 。   

优点:

  就是增加新的操作很容易,因为增加新的操作意味着增加一个新的访问者。访问者模式将有关的行为集中到一个访问者对象中 。   

缺点:

  其实也就是使增加新的数据结构变得困难了。   

右侧可以看目录,点击直接跳转,这个系列一共写了4篇(可以点击下面直接跳转然后看右侧目录):


结语

  设计模式还有几个模式没有弄清楚,用的好像也不是很多,之后我再更新吧!
  每个人吸收知识的时候,都要有抽取精华,去除糟粕的能力。作者所说的,可能有些是对的,有些是错的,有些是适合你的,有些是不太适合你的,你要自己能够判断。

其实你在生活和工作当中也是一样的,你身边的人形形色色,有的人你喜欢,有的人你很讨厌。但其实你喜欢的人也有缺点,你讨厌的人也有优点。你要学会从你讨厌的人身上学会他的优点,千万不要一棒子打死,这只会让你失去很多学习成长的机会。

希望本文可以帮助到作为程序猿或即将成为程序猿的你。


版权声明:本文由 Ian 在 2018年03月08日发表。本文采用CC BY-NC-SA 4.0许可协议,非商业转载请注明出处,不得用于商业目的。
文章题目及链接:《设计模式总结(四)》




  相关文章:


留言区:

TOP