通熟易懂的设计模式(二)

392 阅读11分钟

组合模式(Composite pattern)

组合模式看起来就像对象组的树形结构,一个对象里面包含一个或一组其他的对象。它是属于结构型模式。 例如,一个公司包括很多个部门,每个部门又包括很多人,这个用数据结构来表示就是树形结构,实际上也是用到来组合模式,多个人组成一个部门,多个部门组成一个公司。

例如,我们用下面这个公司、部门、员工的例子来更好的理解组合模式。

class Company {
    private String name;
    private List<Dept> depts;
}

class Dept {
    private String name;
    private List<User> users;
}

class User {
    private String name;
}

装饰模式(Decorator pattern)

装饰器设计模式允许我们动态地向对象添加功能和行为,而不会影响同一类中其他现有对象的行为。并且可以根据我们的要求和选择将此自定义功能应用于单个对象。 假如使用继承来扩展类的行为,这发生在编译期,该类的所有实例都获得扩展行为。

装饰器设计模式的特点: 它允许我们在运行时向对象(而不是类)添加功能。 它是一种结构模式,它为现有类提供了一个包装器。 它使用抽象类或接口与组合来实现包装器。 它创建装饰器类,它包装原始类并通过保持类方法的签名不变来提供其他功能。 它最常用于应用单一责任原则,因为我们将功能划分为具有独特关注区域的类。

例如,我们用下面这个画图形的例子来更好的理解装饰模式。

//定义一个形状的接口
public interface Shape {
    void draw();
    void resize();
}
//一个画圆的实现
public class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing Circle");
    }
    @Override
    public void resize() {
        System.out.println("Resizing Circle");
    }
}
//一个画矩形的实现
public class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing Rectangle");
    }
    @Override
    public void resize() {
        System.out.println("Resizing Rectangle");
    }
}
//定义一个形状的装饰器抽象类,并用组合模式定义一个形状的属性
public abstract class ShapeDecorator implements Shape {
    protected Shape decoratedShape;
    public ShapeDecorator(Shape decoratedShape) {
        super();
        this.decoratedShape = decoratedShape;
    }
}
//颜色的枚举
public enum Color {
    RED,
    GREEN,
    BLUE
}
//线条样式的枚举
public enum LineStyle {
    SOLID,
    DASH,
    DOT
}
//定义一个填充颜色的实现类实现装饰器,并重写 draw() 方法,resize() 方法我们可以保持不变,也可以自定义,看使用场景
public class FillColorDecorator extends ShapeDecorator {
    protected Color color;
    public FillColorDecorator(Shape decoratedShape, Color color) {
        super(decoratedShape);
        this.color = color;
    }
    @Override
    public void draw() {
        decoratedShape.draw();
        System.out.println("Fill Color: " + color);
    }
    @Override
    public void resize() {
      decoratedShape.resize();
    }
}
//定义一个线条样式的实现类实现装饰器,并重写 draw() 方法,resize() 方法我们可以保持不变,也可以自定义,看使用场景
public class LineStyleDecorator extends ShapeDecorator {
    protected LineStyle style;
    public LineStyleDecorator(Shape decoratedShape, LineStyle style) {
        super(decoratedShape);
        this.style = style;
    }
    @Override
    public void draw() {
        decoratedShape.draw();
        System.out.println("Line Style: " + style);
    }
    // 
    @Override
    public void resize() {
        decoratedShape.resize();
    }
}
//使用装饰器模式
public class Client {
    public static void main(String[] args) {
        //在使用时可以任意组装,提升代码灵活性和扩展性。
        Shape circle = new FillColorDecorator(new LineStyleDecorator(new Circle(), LineStyle.DASH), Color.RED);
        circle.draw();
    }
}

外观模式(Facade Pattern)

它提供了一个可以访问系统的接口,这个接口里面的实现可能很复杂,调用了其他多个接口,我们并不知道它里面的具体实现,隐藏了系统的复杂性。它属于结构型模式。

它的优点:
1、减少系统相互依赖。
2、提高灵活性。
3、提高了安全性。

它的缺点:
不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。

以旅行社网站为例,它可以预订酒店和航班,我们来更好的理解下这个模式。

//提供酒店相关的接口
public class HotelBooker{  
    public ArrayList<Hotel> getHotelNamesFor(Date from, Date to)   {  
    //返回该时间段有效的酒店
    }
}
//提供航班相关的接口
public class FlightBooker{  
    public ArrayList<Flight> getFlightsFor(Date from, Date to)   { 
    //返回该时间段有效的航班
    }
}
//提供一个旅行对外的接口,一次返回酒店和航班信息
public class TravelFacade{   
    private HotelBooker hotelBooker;   
    private FlightBooker flightBooker;   
    public void getFlightsAndHotels(Date from, Data to)  {      ArrayList<Flight> flights = flightBooker.getFlightsFor(from, to);                       ArrayList<Hotel> hotels = hotelBooker.getHotelsFor(from, to);         
    }
}
//调用旅行外观模式
public class Client{   
    public static void main(String[] args)   {   
        TravelFacade facade = new TravelFacade();         
        facade.getFlightsAndHotels(from, to);   
    }
}

享元模式(Flyweight Pattern)

享元模式主要用于减少创建对象的数量,以减少内存占用和提高性能。使许多细粒度对象的重用,使得大量对象的利用更加有效。它属于结构型模式。

它的优点:
大大减少对象的创建,降低系统的内存,使效率提高。

它的缺点:
因为一个对象大家共享,最好就不要有状态区分,假如有状态的话, 需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。

例如,我们用一个画线条的例子来更好的理解这个模式。

//定义一个画线条的接口
public interface LineFlyweight{
    public Color getColor();
    public void draw(Point location);
}
//定义一个画线条的实现
public class Line implements LineFlyweight{
    private Color color; 
    public Line(Color c){
        color = c;
    }
    public Color getColor(){
        return color;
    }
    public void draw(Point location){
        //实现
    }
}
//定一个画线条的工厂类,根据颜色来获取线条,并且把不同颜色的线存储在一个 pool 中,方便下次直接使用
public class LineFlyweightFactory{
    private List<LineFlyweight> pool; 
    public LineFlyweightFactory(){
        pool = new ArrayList<LineFlyweight>();
    }
    public LineFlyweight getLine(Color c){
        //循环检查这个颜色的线是否存在,存在直接返回
        for(LineFlyweight line: pool){
            if(line.getColor().equals(c)){
                return line;
            }
        }
        //假如不存在,就创建一个放入这个 pool 中,方便下次直接使用
        LineFlyweight line = new Line(c);
        pool.add(line);
        return line;
    }
}
//调用享元模式
public class Client{   
    public static void main(String[] args)   {  
        //调用
        LineFlyweightFactory factory = new LineFlyweightFactory(); 
        LineFlyweight line = factory.getLine(Color.RED); 
        LineFlyweight line2 = factory.getLine(Color.RED); 
        line.draw(new Point(100, 100));
        line2.draw(new Point(200, 100));
    }
}

代理模式(Proxy Pattern)

它通过一个代理类对外提供访问,代理类在真正调用实现类。对外部来说,并不知道真正实现类的详情,提高类系统的安全性。我们可以在代理类里做一系列拦截,把异常的请求都提前处理掉,扩展性很高。它属于结构型模式。

它的优点:
1、职责清晰。
2、高扩展性。
3、更安全。

它的缺点:
1、由于在客户端和真实主题之间增加了代理对象,因此可能会造成请求的处理速度变慢。
2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

例如,我们用下面这个代理显示图片的例子来更好的理解代理模式。

//定义的图片接口
public interface Image{ 
  public void displayImage();
}
//真正的实现类
public class RealImage implements Image{   
    public RealImage(URL url)   {      
        //加载这个图片    
        loadImage(url);   
    }   
    public void displayImage()   {       
    //显示这个图片 
    }   
    private void loadImage(URL url)  {      
     
    }
}
//代理类
public class ProxyImage implements Image{    
    private URL url;     
    public ProxyImage(URL url)    {       
        this.url = url;    
    }    
    //this method delegates to the real image    
    public void displayImage()   {        
        RealImage real = new RealImage(url);  
        real.displayImage();    
    }
}
//使用代理模式
public class Client {
   public static void main(String[] args) {
      Image image = new ProxyImage("test.png");
      image.display();  
   }
}

代理模式分为静态代理和动态代理,静态代理的真正实现类是提前写好并且编译好的,动态代理的真正实现类是运行是生成并且编译的,上面的例子使用的是静态代理。
动态代理又分为 JDK动态代理 和 CGLIB动态代理,JDK动态代理是基于接口的动态代理,CGLIB动态代理是基于类的代理。有兴趣的可以找下相关资料。

责任链模式(Chain of Responsibility Pattern)

责任链模式用于管理对象之间的算法,关系和责任,通过将多个不同处理对象链接在一起处理请求,降低耦合度,提高系统灵活性。它属于行为型模式。

它的优点:
1、降低耦合度。
2、简化了对象。
3、增强给对象指派职责的灵活性。
4、增加新的请求处理类很方便。

它的缺点:
1、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。
2、可能不容易观察运行时的特征,不方便排错。

例如,我们用下面这个记录日志的例子来更好的理解责任链模式。

//定义一个抽象的日志接口,并且提供一个可以设置下一个处理日志对象的方法
public abstract class AbstractLogger {
   public static int INFO = 1;
   public static int DEBUG = 2;
   public static int ERROR = 3;
   protected int level;
   //责任链中的下一个元素
   protected AbstractLogger nextLogger;
   public void setNextLogger(AbstractLogger nextLogger){
      this.nextLogger = nextLogger;
   }
   public void logMessage(int level, String message){
      if(this.level <= level){
         write(message);
      }
      if(nextLogger !=null){
         nextLogger.logMessage(level, message);
      }
   }
    //抽象方法
   abstract protected void write(String message);
}
//定义一个标准日志的实现类
public class ConsoleLogger extends AbstractLogger {
   public ConsoleLogger(int level){
      this.level = level;
   }
   @Override
   protected void write(String message) {    
      System.out.println("Standard Console::Logger: " + message);
   }
}
//定义一个错误日志的实现类
public class ErrorLogger extends AbstractLogger {
   public ErrorLogger(int level){
      this.level = level;
   }
   @Override
   protected void write(String message) {    
      System.out.println("Error Console::Logger: " + message);
   }
}
//定义一个文件日志的实现类
public class FileLogger extends AbstractLogger {
   public FileLogger(int level){
      this.level = level;
   }
   @Override
   protected void write(String message) {    
      System.out.println("File::Logger: " + message);
   }
}
//使用责任链模式
public class Client {
    //设置责任链的调用顺序
   private static AbstractLogger getChainOfLoggers(){
      AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR);
      AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG);
      AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO);
      
      errorLogger.setNextLogger(fileLogger);
      fileLogger.setNextLogger(consoleLogger);
      return errorLogger;  
   }
 
   public static void main(String[] args) {
      AbstractLogger loggerChain = getChainOfLoggers();
      loggerChain.logMessage(AbstractLogger.INFO, "info level information.");
      loggerChain.logMessage(AbstractLogger.DEBUG, 
         "debug level information.");
      loggerChain.logMessage(AbstractLogger.ERROR, 
         "error information.");
   }
}

命令模式(Command Pattern)

命令模式是请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。它属于行为型模式。

它的优点:
1、降低了系统耦合度。
2、新的命令可以很容易添加到系统中去。

它的缺点:
使用命令模式可能会导致某些系统有过多的具体命令类。

例如,我们用下面这个开关灯的例子来更好的理解命令模式。

// 定义执行命令接口
public interface Command{
  public void execute();
}
//开灯命令实现类
public class LightOnCommand implements Command{
  Light light;
  public LightOnCommand(Light light){
    this.light = light;
  }
  public void execute(){
    light.switchOn();
  }
}
//关灯命令实现类
public class LightOffCommand implements Command{
  Light light;
  public LightOffCommand(Light light){
    this.light = light;
  }
  public void execute(){
    light.switchOff();
  }
}
//真正开关灯的类
public class Light{
  private boolean on;
  public void switchOn(){
    on = true;
  }
  public void switchOff(){
    on = false;
  }
}
//根据不同命令执行开关灯
public class RemoteControl{
  private Command command;
  public void setCommand(Command command){
    this.command = command;
  }
  public void pressButton(){
    command.execute();
  }
}
//调用命令模式
public class Client{
  public static void main(String[] args){
    RemoteControl control = new RemoteControl();
    Light light = new Light();
    Command lightsOn = new LightsOnCommand(light);
    Command lightsOff = new LightsOffCommand(light);
    //设置开灯命令来开灯
    control.setCommand(lightsOn);
    control.pressButton();
    //设置关灯命令来关灯
    control.setCommand(lightsOff);
    control.pressButton();
  }
}

解释器模式(Interpreter Pattern)

解释器模式是给定一种语言,定义其语法以及使用该语法来表示语言中句子的解释器。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。它属于行为型模式。

它的优点:
1、可扩展性比较好,灵活。
2、增加了新的解释表达式的方式。
3、易于实现简单语法。

它的缺点:
1、可利用场景比较少。
2、对于复杂的文法比较难维护。
3、解释器模式会引起类膨胀。
4、解释器模式采用递归调用方法。

例如,我们用下面这个简单规则表达式的例子来更好的理解解释器模式。

//定义一个表达式接口
public interface Expression {
   public boolean interpret(String context);
}
//定义一个基本规则的实现,输入的内容包含初始化的内容时,返回 true
public class TerminalExpression implements Expression {
   private String data;
   public TerminalExpression(String data){
      this.data = data; 
   }
   @Override
   public boolean interpret(String context) {
      if(context.contains(data)){
         return true;
      }
      return false;
   }
}
//定义一个或者规则的实现,输入的内容包含初始化时任意一个内容时,返回 true,否则 false
public class OrExpression implements Expression {
   private Expression expr1 = null;
   private Expression expr2 = null;
   public OrExpression(Expression expr1, Expression expr2) {
      this.expr1 = expr1;
      this.expr2 = expr2;
   }
   @Override
   public boolean interpret(String context) {      
      return expr1.interpret(context) || expr2.interpret(context);
   }
}
//定义一个并且规则的实现,输入的内容同时包含初始化时两个内容时,返回 true,否则 false
public class AndExpression implements Expression {
   private Expression expr1 = null;
   private Expression expr2 = null;
   public AndExpression(Expression expr1, Expression expr2) { 
      this.expr1 = expr1;
      this.expr2 = expr2;
   }
 
   @Override
   public boolean interpret(String context) {      
      return expr1.interpret(context) && expr2.interpret(context);
   }
}
//调用规则表达式
public class Client {
   //规则:Robert 和 John 是男性,输入只要满足其中一个规则就行
   public static Expression getMaleExpression(){
      Expression robert = new TerminalExpression("Robert");
      Expression john = new TerminalExpression("John");
      return new OrExpression(robert, john);    
   }
   //规则:Julie 是一个已婚的女性,输入只要满足两个规则
   public static Expression getMarriedWomanExpression(){
      Expression julie = new TerminalExpression("Julie");
      Expression married = new TerminalExpression("Married");
      return new AndExpression(julie, married);    
   }
 
   public static void main(String[] args) {
      Expression isMale = getMaleExpression();
      Expression isMarriedWoman = getMarriedWomanExpression();
 
      System.out.println("John is male? " + isMale.interpret("John"));
      System.out.println("Julie is a married women? " 
      + isMarriedWoman.interpret("Married Julie"));
   }
}

最后,还是那句话,每种设计模式有它自己的好处,也有它的坏处。在写代码时,多思考,想好在写,免得返工,先从思维方式上改变。使用单一原则,一个类,一个方法只做一件事情,这样方便维护,耦合低,可扩展。

PS:
清山绿水始于尘,博学多识贵于勤。
微信公众号:「清尘闲聊」。
欢迎一起谈天说地,聊代码。