浅谈设计模式1——策略模式 | 适配器模式 | 工厂模式

前言

最近在看《Think In JAVA》,其中在讲解继承,组合,抽象类和接口的时候,提到了题中的几个设计模式。这几个设计模式也确实让我更好的理解了JAVA中各个数据结构的含义。今天就结合书本还有本身的理解,稍微整理一下这几个设计模式。html

Strategy Pattern | 策略模式

这里就必需要提一下向上转化这个概念。在继承和接口中都有提到这个概念。
向上转化在继承中是指子类能够向上转化为父类。好比,有一个Instrument类,以及它的一个子类Flute。子类重写的play方法会覆盖父类的方法。java

class Instrument{
        public void play(){
            ...
            //play instrument
        }
    }
    
    class Flute extends Instrument{
        public void play(){
            ...
            //play flute
        }
    }

若是这时有一个方法须要接收一个乐器参数并演奏,那么无需写多个重载方法接收各类不一样的乐器,只须要一个接收Instrument类的方法。算法

public void play(Instrument instrument){
        instrument.play();
    }

在这个方法中传入一个Flute对象,调用的将是flute中的play方法。这就是向上转化,动态绑定的一个最简单的例子。
其实接口也是同理,只是接口容许多种向上转化。也就是说,JAVA中继承是惟一的,而接口是能够Implement多个的。所以JAVA中继承向上转化的路径惟一,而接口向上转化路径不惟一。spring

接下来就要讲到策略模式。sql

策略模式的概念以下:数据库

Defines a set of encapsulated algorithms that can be swapped to carry out a specific behaviour
定义了一组封装好的算法,这些算法分别执行不一样的操做。在实际运行中,这些算法能够动态切换来知足不一样场景下的需求编程

策略模式的使用情景有:设计模式

  1. 将文件保存为不一样的格式安全

  2. 排序算法的多种实现数据结构

  3. 文件压缩的多种实现

也就是说,策略模式将一组完成相同工做的不一样方式的代码分别放到不一样的类中,并经过策略模式实如今运行中的相互切换。

clipboard.png

这是从网上找到的关于策略模式的UML图。
策略模式是JAVA中继承,抽象类以及接口的一种综合应用。在策略模式中,咱们能够根据一个“开放接口”设计出多种“具体策略”,而后在调用时只须要输入“开放接口”,程序运行时会根据“开放接口”的具体实现来决定具体的运行结果。

上面设计模式的代码以下:

//接口
public interface Strategy {
   public int doOperation(int num1, int num2);
}

//接口实现类
public class OperationAdd implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 + num2;
   }
}
public class OperationSubstract implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 - num2;
   }
}
public class OperationMultiply implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 * num2;
   }
}

//上下文
public class Context {
   private Strategy strategy;

   public Context(Strategy strategy){
      this.strategy = strategy;
   }

   public int executeStrategy(int num1, int num2){
      return strategy.doOperation(num1, num2);
   }
}

//具体调用
public class StrategyPatternDemo {
   public static void main(String[] args) {
      Context context = new Context(new OperationAdd());        
      System.out.println("10 + 5 = " + context.executeStrategy(10, 5));

      context = new Context(new OperationSubstract());        
      System.out.println("10 - 5 = " + context.executeStrategy(10, 5));

      context = new Context(new OperationMultiply());        
      System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
   }
}

下面我再讲一个具体的例子来讲明使用策略模式与不使用策略模式的差距。

假设咱们有一个压缩文件的功能,压缩文件有多种算法,如Zip,RAR等等。程序可以根据实际的操做系统以及性能等参数来选择一个压缩算法执行压缩操做。咱们假设这个选择具体算法的功能放置CompressionPreference类中。
其实这些对于客户端来讲都是透明的。也就是说,客户端只知道会有一个压缩功能,该功能须要客户上传要压缩的文件。如此场景下,服务端只须要提供一个压缩的接口,而无需暴露具体的实现。

代码以下:

//选择压缩方法类,根据具体状况返回压缩的方法
public class CompressionPreference{
     public static CompressionStrategy getPreferedStrategy(){
         //根据系统的状况或是用户的选择返回具体的压缩算法
     }
}

//压缩策略接口
public interface CompressionStrategy{
    void compress(List<File> files);
}

//压缩策略的具体实现
public class ZipCompressionStrategy implements CompressionStrategy{
    @Override
    public void compress(List<File> files){
        //zip 压缩
    }
}

public class RarCompressionStrategy implements CompressionStrategy{
    @Override
    public void compress(List<File> files){
        //RAR 压缩
    }
}

public class CompressionContext{
    private CompressionStrategy strategy;
    
    //这里根据CompressionPreference选择压缩策略
    public void setStrategy(CompressionStrategy strategy){this.strategy=strategy);}
    
    public void createArchieve(List<File> files){
        strategy.compress(files);
    }
}

//客户端调用
public class Client{
    public static void main(String[] args) {
    CompressionContext ctx = new CompressionContext();
    //设置压缩上下文
    ctx.setCompressionStrategy(CompressionPreference.getPreferedStrategy());
    ctx.createArchive(fileList);
  }
}

经过这样的设计以后,若是须要添加新的算法,只须要增长一个CompressionStrategy的具体实现类,以及修改一下CompressionPreference中的方法便可。对于客户端的调用不会产生任何影响。

若是不对算法进行封装,直接容许客户端调用的话。一方面,暴露了压缩算法的种种实现,另外一方面,也增长了可能形成的错误调用。并且一旦增长新的压缩算法,客户端也须要知道这些本不须要知道的东西,调整本身的调用。这样的代码,可维护性实在是太差了。

适配器模式 | Adapter Design Pattern

定义:

Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.
将两个不想兼容的接口经过适配器进行相互转化。

适配器模式要比策略模式要好理解一些。在书中讲解适配器模式时,其实是为了补充说明如何面向接口编程。适配器模式,顾名思义,就是将原本并不继承某个接口的类经过适配器转化为能够经过该接口调用,它充当着连个不兼容的接口之间的桥梁。

从原含义上来说,适配器是指一个接口转换器,在生活中最多见的接口转换器就是你的手机充电线头啦!充电头将从插座中输出的标准220V电压(国内)转化为能够安全充电的电压。而且在另外一侧提供了一个USB充电口,从而使手机能够在一切含有USB端口的充电线下进行充电。再举一个例子,也就是SD卡。使用相机的朋友知道,有些电脑是不提供SD卡接口的,那么就须要将SD卡插入SD卡读卡器,再将读卡器经过USB接口插入电脑。这时电脑就能够读取SD卡中的内容了。

在书中的例子,适配器应用的场景是将不能够修改的类改成继承某个接口从而能够做为该接口的一个实现类被调用。书中的适配器模式有两种实现方式,一种是经过代理,另外一种是经过继承。两种方式本质上是相同的,若是须要原类中的全部实现,则经过继承方式实现适配器,若是只是一部分实现,则经过代理的方式。具体状况具体分析。


以上讲的都太过抽象了,下面讲一个具体的例子。

好比我有一个扫描类Scanner,他有一个方法,能够接收全部继承了Readable接口的类,并根据类中的状况将其中的数据读取出来。系统中已经有一些类,他们是能够被Scanner读取的,可是他们并无继承Readable接口。本着开闭原则,我能够给这些类添加一个适配器,使其能够被Scanner读取。经过这种模式,不管是出现新的Scanner读取文件或是读取系统已有的文件,都没必要修改Scanner方法。只须要使其支持Readable接口就行。

public class Scanner{
        public void read(Readable material){
            material.read();
        }
    }
    
    public interface Readable{
        void read();
    }
    public class TXT implements Readable{
        ...
        public void read(){
            //读取txt文件
        }
        ...
    }
    
    public class HTML{
        public void toReadableFormat(){
            //html文件也能够被读取,可是它并无继承Readable接口,因此没法被Scanner
            识别
        }
    }
    
    //这里才是适配器模式
    public class HTMLAdapter implements Readable{
        ...
        private HTML html;
        public HTMLAdapter(HTML html){this.html = html}
        public void read(){
            html.toReadableFormat();
        }
        ...
    }
    
    //这时候两个文件均可以被读取了
    public class Test{
        public static void main(String[] args){
            Scanner s = new Scanner();
            s.read(new TXT());
            s.read(new HTMLAdapter(new HTML()));
        }
    }

一个例子不够,再来一个~

在自媒体的发展史中,媒体的格式愈来愈多样化,从最初的文本,到MP3,再到视频格式。若是如今有一个系统,它原本只支持MP3格式的文件的读取,这时候要想该系统能够支持新媒体类的文件的播放。新媒体类文件由另外一个团队开发,拥有本身的开发接口和具体实现。如何才能将该模块融入到现有系统中呢?

这时候就须要经过适配器模式来解决这个问题了。

clipboard.png

这是这个系统给的UML类图。经过在原系统中新建一个MediaAdapter适配器继承原媒体播放器的接口,从而使原系统能够在不知下层变更的基础上,继续调用原来的play方法来实现播放功能。
具体代码以下:

public interface MediaPlayer {
       public void play(String audioType, String fileName);
    }
    
    public interface AdvancedMediaPlayer {    
       public void playVlc(String fileName);
       public void playMp4(String fileName);
    }
    
    public class VlcPlayer implements AdvancedMediaPlayer{
       @Override
       public void playVlc(String fileName) {
          System.out.println("Playing vlc file. Name: "+ fileName);        
       }

       @Override
       public void playMp4(String fileName) {
          //do nothing
       }
    }
    
    public class Mp4Player implements AdvancedMediaPlayer{

       @Override
       public void playVlc(String fileName) {
          //do nothing
       }

       @Override
       public void playMp4(String fileName) {
          System.out.println("Playing mp4 file. Name: "+ fileName);        
       }
    }
    
    public class MediaAdapter implements MediaPlayer {

       AdvancedMediaPlayer advancedMusicPlayer;

       public MediaAdapter(String audioType){
   
          if(audioType.equalsIgnoreCase("vlc") ){
             advancedMusicPlayer = new VlcPlayer();            
         
          }else if (audioType.equalsIgnoreCase("mp4")){
             advancedMusicPlayer = new Mp4Player();
          }    
       }

       @Override
       public void play(String audioType, String fileName) {
   
          if(audioType.equalsIgnoreCase("vlc")){
             advancedMusicPlayer.playVlc(fileName);
          }
          else if(audioType.equalsIgnoreCase("mp4")){
             advancedMusicPlayer.playMp4(fileName);
          }
       }
    }
    
    public class AdapterPatternDemo {
       public static void main(String[] args) {
          MediaPlayer audioPlayer = new AudioPlayer();
          audioPlayer.play("mp3", "beyond the horizon.mp3");
          MediaPlayer videoPlayer = new MediaAdapter();
          videoPlayer.play("vlc", "far far away.vlc");
   }
}

工厂模式 | Factory Design Pattern

终于到工厂模式了~~~~写了很久了呀QAQ

工厂模式是为了管理一个接口之下众多实现类。好比最多见的DAO接口。数据库中的表少至10个多能够至百个。在spring框架中,经过依赖倒置和自动注入实现了这么多读取数据库的接口实现类的管理。那么在没有框架的场景下,如何才可使上层代码和下层具体的DAO接口解耦呢?这时就须要工厂模式。经过工厂模式得到具体DAO接口。

至于为何要选择这样的一个工厂模式,而不是直接new一个具体的实现类呢?这里举个例子。比方说,有一个DAO接口,实现该接口的有UserDaoImpl, AccountDaoImpl等。假设有两个类均用到UserDaoImpl。若是在这两个类中均使用new来建立一个新的UserDaoImpl,那么一旦有一天,由于需求变动,须要将UserDaoImpl换成AnotherUserDaoImpl,则须要在两个类中分别修改。那么若是有十个类,甚至一百个类都用到了这个Dao呢?这时候若是我是经过工厂来得到这个Dao,也就只须要在工厂中将返回值从原来的UserDaoImpl变成AnotherUserDaoImpl,并不会影响调用方。


简单工厂模式 | Static Factory Method

下面给一个简单的工厂模式的例子。

interface Dog
{
  public void speak ();
}

class Poodle implements Dog
{
  public void speak()
  {
    System.out.println("The poodle says \"arf\"");
  }
}

class Rottweiler implements Dog
{
  public void speak()
  {
    System.out.println("The Rottweiler says (in a very deep voice) \"WOOF!\"");
  }
}

class SiberianHusky implements Dog
{
  public void speak()
  {
    System.out.println("The husky says \"Dude, what's up?\"");
  }
}

class DogFactory
{
  public static Dog getDog(String criteria)
  {
    if ( criteria.equals("small") )
      return new Poodle();
    else if ( criteria.equals("big") )
      return new Rottweiler();
    else if ( criteria.equals("working") )
      return new SiberianHusky();

    return null;
  }
}

public class JavaFactoryPatternExample
{
  public static void main(String[] args)
  {
    // create a small dog
    Dog dog = DogFactory.getDog("small");
    dog.speak();

    // create a big dog
    dog = DogFactory.getDog("big");
    dog.speak();

    // create a working dog
    dog = DogFactory.getDog("working");
    dog.speak();
  }
}

在简单的工厂模式中,工厂根据输入的条件返回给一个接口的具体实现。
简单工厂模式有一个问题,就是一旦工厂出现新的产品,就必须修改工厂中获取产品的方法,这有违开闭原则。并且工厂模式承担的压力太重,可能会致使职责的混乱。最重要的是,简单工厂模式中,获取产品的方法是静态方法,该方法没法经过继承等形式获得扩展。


工厂方法模式 | Factory Method Pattern

这实际上是工厂模式的一个简单的升级。考虑一个真实工厂的场景。它的产品Product之下每每还有许多分类,如轴承,轮胎。各个子分类每每也对应着不一样的车间,如轴承车间,轮胎车间。若是还用简单工厂模式返回一个Product,且不说向上转型可能丢失的一些数据,并且工厂的压力也太大了,由于可能要根据不一样场景返回上百个不一样类型但继承了同一接口的类。这不符合设计原则。

这时候就出现了工厂方法模式。不只仅对产品抽象,还对工厂抽象。对不一样的产品提供不一样的工厂,将职责进一步细化,知足SRP(单一职责原则)。同时,由于不须要输入无关的判断数据,也解除了控制耦合。

具体例子有最多见的日志系统。日志系统之下每每针对各个不一样的子系统,好比数据库日志子系统,好比文件日志子系统。不一样的日志系统对应的日志文件也不一样。这时经过工厂方法模式就能够很好的解决两者之间的关系。

clipboard.png

在这张图中还能够继续延伸,好比数据库包括Mysql数据库,Oracle数据库等等。在UML图中也能够继续根据MySqlLog建立MysqlLogFactory。

还有一个具体的例子就是JAVA中的数据库链接。

Connection conn=DriverManager.getConnection("jdbc:microsoft:sqlserver://loc
alhost:1433; DatabaseName=DB;user=sa;password=");
Statement statement=conn.createStatement();
ResultSet rs=statement.executeQuery("select * from UserInfo");

这里经过DriverManager工厂根据输入的信息返回一个对应的链接。链接中再返回对应的抽象语句statement。根据工厂中的信息能够知道,这个statement的底层实现一定是一个相似SqlServerStatement的实现。

抽象工厂模式 | Abstract Factory Method

产品等级结构:产品等级结构即产品的继承结构,如一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌的电视机是其子类。
产品族:在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不一样产品等级结构中的一组产品,如海尔电器工厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中。

抽象工厂模式与工厂方法模式最大的区别在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则须要面对多个产品等级结构,一个工厂等级结构能够负责多个不一样产品等级结构中的产品对象的建立 。当一个工厂等级结构能够建立出分属于不一样产品等级结构的一个产品族中的全部对象时,抽象工厂模式比工厂方法模式更为简单、有效率。

在这里的上下文中,抽象工厂之下的子工厂被划分为海尔子工厂,海信子工厂。在海尔自工厂中能够得到海尔冰箱(productA),海尔电视机(productB), 同理,在海信子工厂中,能够得到海信冰箱(productA),海信电视机(productB)。

固然了 在大多数的应用场景下,工厂设计模式已经足够了。

clipboard.png

References

Tutorialspoint Design Pattern
stackoverflow : how does the strategy design pattern work
dzon strategy design pattern
dzon adapter pattern
工厂模式的一个简单例子
工厂模式的一篇极好博客
几种工厂模式之间的对比

相关文章
相关标签/搜索