浅尝设计模式

设计模式三大类

创建型模式

工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式

结构型模式

适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式

行为型模式

策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式

设计模式详解

设计模式的六大原则

总原则:开闭原则(Open Close Principle)

开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,而是要扩展原有代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类等,后面的具体设计中我们会提到这点。

1、单一职责原则

不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,如若不然,就应该把类拆分。

2、里氏替换原则(Liskov Substitution Principle)

里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

里氏替换原则中,子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。

3、依赖倒转原则(Dependence Inversion Principle)

这个是开闭原则的基础,具体内容:面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。

4、接口隔离原则(Interface Segregation Principle)

这个原则的意思是:每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。

5、迪米特法则(最少知道原则)(Demeter Principle)

就是说:一个类对自己依赖的类知道的越少越好。也就是说无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。

最少知道原则的另一个表达方式是:只与直接的朋友通信。类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌生的类不要作为局部变量出现在类中。

6、合成复用原则(Composite Reuse Principle)

合成复用原则是尽量首先使用合成/聚合的方式,而不是使用继承。

单例模式

单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

单例模式是一种常见的设计模式,在计算机系统中,线程池、缓存、日志对象、对话框、打印机、数据库操作、显卡的驱动程序常被设计成单例。

单例模式的要点有三个

  1. 只能有一个实例,必须拥有一个构造函数,并且必须被标记为private
  2. 必须自行创建这个实例,拥有一个保存类的实例的静态成员变量
  3. 必须给其他对象提供这一实例,拥有一个访问这个实例的公共的静态方法

单例类不能在其它类中直接实例化,只能被其自身实例化。它不会创建实例副本,而是会向单例类内部存储的实例返回一个引用。在PHP中,为防止对单例类对象的克隆来打破单例类的上述实现形式,通常还为基提供一个空的私有__clone()方法。

为什么要使用PHP单例模式

  1. php的应用主要在于数据库应用, 一个应用中会存在大量的数据库操作, 在使用面向对象的方式开发时, 如果使用单例模式, 则可以避免大量的new 操作消耗的资源,还可以减少数据库连接这样就不容易出现 too many connections情况。
  2. 如果系统中需要有一个类来全局控制某些配置信息, 那么使用单例模式可以很方便的实现.
  3. 在一次请求中, 便于进行调试, 因为所有的代码(如数据库操作类db)都集中在一个类中, 我们可以在类中设置钩子, 输出日志,从而避免到处查找问题。
  4. global全局变量,也起到单例模式的作用,但在OOP中,我们建议拒绝这种编码。因为global存在安全隐患(全局变量不受保护的本质)。全局变量是面向对象程序员遇到的引发BUG的主要原因之一。这是因为全局变量将类捆绑于特定的环境,破坏了封装。如果新的应用程序无法保证一开始就定义了相同的全局变量,那么一个依赖于全局变量的类就无法从一个应用程序中提取出来并应用到新应用程序中。确切的讲,单例模式恰恰是对全局变量的一种改进,避免那些存储唯一实例的全局变量污染命名空间。你无法用错误类型的数据覆写一个单例。这种保护在不支持命名空间的PHP版本里尤其重要。因为在PHP中命名冲突会在编译时被捕获,并使脚本停止运行。

实现代码如下

/**
  * 设计模式之单例模式
  * $_instance必须声明为静态的私有变量
  * 构造函数必须声明为私有,防止外部程序new类从而失去单例模式的意义
  * getInstance()方法必须设置为公有的,必须调用此方法以返回实例的一个引用
  * ::操作符只能访问静态变量和静态函数
  * new对象都会消耗内存
  * 使用场景:最常用的地方是数据库连接。
  * 使用单例模式生成一个对象后,该对象可以被其它众多对象所使用。
  */
 class man
 {
     //保存例实例在此属性中
     private static $_instance;
     
     //构造函数声明为private,防止直接创建对象
     private function __construct() {}
 
     //单例方法  核心
     public static function get_instance()
     {
         //var_dump(isset(self::$_instance));
         if(!isset(self::$_instance))
         {
             self::$_instance=new self();
         }
         return self::$_instance;
     }
 
     //阻止用户复制对象实例
     private function __clone()  {}
 
     function test()
     {
         echo("test");
     }
 }
 
 // 这个写法会出错,因为构造方法被声明为private
 //$test = new man;
 
 // 下面将得到Example类的单例对象  $test和$test1是同一个实例
 $test = man::get_instance();
 $test1 = man::get_instance();
 $test->test();
 
 // 复制对象将什么都不操作
 //$test_clone = clone $test;

单例模式的优缺点

优点:

  1. 改进系统的设计
  2. 是对全局变量的一种改进
  3. 节约内存空间资源

缺点:

  1. 难于调试
  2. 隐藏的依赖关系
  3. 无法用错误类型的数据覆写一个单例

工厂模式

建立一个工厂(一个函数或一个类方法)来制造新的对象

  • 工厂模式就是一种类,是指包含一个专门用来创建其他对象的方法的类。工厂类在多态性编程实践中是至关重要的,它允许动态的替换类,修改配置,通常会使应用程序更加灵活,熟练掌握工厂模式高级PHP开发人员是很重要的。
  • 工厂模式通常用来返回符合类似接口的不同的类,工厂的一种常见用法就是创建多态的提供者,从而允许我们基于应用程序逻辑或者配置设置来决定应实例化哪一个类,例如,可以使用这样的提供者来扩展一个类,而不需要重构应用程序的其他部分,从而使用新的扩展后的名称 。
  • 通常,**工厂模式有一个关键的构造,根据一般原则命名为Factory的静态方法,然而这只是一种原则,工厂方法可以任意命名,这个静态还可以接受任意数据的参数,必须返回一个对象。**具有为您创建对象的某些方法,这样就可以使用工厂类创建对象,工厂模式在于可以根据输入参数或者应用程序配置的不同来创建一种专门用来实现化并返回其它类的实例的类,而不直接使用new,这样如果想更改创建的对象类型,只需更改该工厂即可。

简单的说,PHP工厂模式就是用一个工厂方法来替换掉直接new对象的操作,就是为方便扩展,方便使用,在新增实现基类中的类中方法时候,那么在工厂类中无需修改,传入参数可以直接使用(这个是工厂模式),具体就是跳过工厂类修改,直接使用工厂类输出想要的结果。

工厂类基础要素

  • ①抽象基类:类中定义抽象一些方法,用以在子类中实现
  • ②继承自抽象基类的子类:实现基类中的抽象方法
  • ③工厂类:用以实例化所有相对应的子类
    img

代码实现

/**
 * 定义个抽象的类,让子类去继承实现它  或者定义一个接口
 */
abstract class Operation{
    //抽象方法不能包含函数体 强烈要求子类必须实现该功能函数
    abstract public function getValue($num1,$num2);
}

/**
 * 加法类
 */
class OperationAdd extends Operation {
    public function getValue($num1,$num2){
        return $num1+$num2;
    }
}
/**
 * 减法类
 */
class OperationSub extends Operation {
    public function getValue($num1,$num2){
        return $num1-$num2;
    }
}
/**
 * 乘法类
 */
class OperationMul extends Operation {
    public function getValue($num1,$num2){
        return $num1*$num2;
    }
}
/**
 * 除法类
 */
class OperationDiv extends Operation {
    public function getValue($num1,$num2){
        try {
            if ($num2===0){
                throw new Exception('除数不能为0');
            }else {
                return $num1/$num2;
            }
        }catch (Exception $e){
            echo '错误信息:'.$e->getMessage();
        }
    }
}

通过采用面向对象的继承特性,我们可以很容易就能对原有程序进行扩展,比如:‘乘方’,‘开方’,‘对数’,‘三角函数’,‘统计’等,以还可以避免加载没有必要的代码。

如果我们现在需要增加一个求余的或者除法运算的类,会非常的简单。我们只需要另外写一个类(该类继承虚拟基类),在类中完成相应的功能(比如:求乘方的运算),而且大大的降低了耦合度,方便日后的维护及扩展 。

/**
  * 求余类(remainder)
  */
 class OperationRem extends Operation {
     public function getValue($num1,$num2){
         return $num1%$num12;
     }
 }

接下来就是如何让程序根据用户输入的操作符实例化相应的对象了。解决办法:使用一个单独的类来实现实例化的过程,这个类就是工厂

/**
  * 工程类,主要用来创建对象
  * 功能:根据输入的运算符号,工厂就能实例化出合适的对象
  */
  class Factory{
      public static function createObj($operate){
          switch ($operate){
              case '+':
                  return new OperationAdd();
                  break;
              case '-':
                  return new OperationSub();
                  break;
              case '*':
                  return new OperationSub();
                  break;
              case '/':
                  return new OperationDiv();
                  break;
          }
      }
  }
  $test=Factory::createObj('/');
  $result=$test->getValue(23,0);
  echo $result;

抽象工厂模式

抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在抽象工厂模式中,**接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。**每个生成的工厂都能按照工厂模式提供对象。

关键代码:在一个工厂里聚合多个同类产品。

应用实例

工作了,为了参加一些聚会,肯定有两套或多套衣服吧,比如说有商务装(成套,一系列具体产品)、时尚装(成套,一系列具体产品),甚至对于一个家庭来说,可能有商务女装、商务男装、时尚女装、时尚男装,这些也都是成套的,即一系列具体产品。假设一种情况(现实中是不存在的,要不然,没法进入共产主义了,但有利于说明抽象工厂模式),在您的家中,某一个衣柜(具体工厂)只能存放某一种这样的衣服(成套,一系列具体产品),每次拿这种成套的衣服时也自然要从这个衣柜中取出了。用 OOP 的思想去理解,所有的衣柜(具体工厂)都是衣柜类的(抽象工厂)某一个,而每一件成套的衣服又包括具体的上衣(某一具体产品),裤子(某一具体产品),这些具体的上衣其实也都是上衣(抽象产品),具体的裤子也都是裤子(另一个抽象产品)。

**优点:**当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。

**缺点:**产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。

使用场景

  1. QQ 换皮肤,一整套一起换。
  2. 生成不同操作系统的程序。

**注意事项:**产品族难扩展,产品等级易扩展。

实现

我们将创建 ShapeColor 接口和实现这些接口的实体类。下一步是创建抽象工厂类 AbstractFactory。接着定义工厂类 ShapeFactoryColorFactory,这两个工厂类都是扩展了 AbstractFactory然后创建一个工厂创造器/生成器类 FactoryProducer

AbstractFactoryPatternDemo 类使用 FactoryProducer 来获取 AbstractFactory 对象。它将向 AbstractFactory 传递形状信息 ShapeCIRCLE / RECTANGLE / SQUARE),以便获取它所需对象的类型。同时它还向 AbstractFactory 传递颜色信息 ColorRED / GREEN / BLUE),以便获取它所需对象的类型。

抽象工厂模式的 UML 图

## 步骤 1 为形状创建一个接口。
Shape.java
public interface Shape {
   void draw();
}

## 步骤 2 创建实现接口的实体类。
Rectangle.java //长方形
public class Rectangle implements Shape {
   @Override
   public void draw() {
      System.out.println("Inside Rectangle::draw() method.");
   }
}

Square.java  //正方形
public class Square implements Shape {
   @Override
   public void draw() {
      System.out.println("Inside Square::draw() method.");
   }
}

Circle.java  //圆形
public class Circle implements Shape {
   @Override
   public void draw() {
      System.out.println("Inside Circle::draw() method.");
   }
}

## 步骤 3  为颜色创建一个接口。
Color.java
public interface Color {
   void fill();
}

##  步骤4  创建实现接口的实体类。
Red.java //红色
public class Red implements Color {
   @Override
   public void fill() {
      System.out.println("Inside Red::fill() method.");
   }
}

Green.java //绿色
public class Green implements Color {
   @Override
   public void fill() {
      System.out.println("Inside Green::fill() method.");
   }
}

Blue.java //蓝色
public class Blue implements Color {
   @Override
   public void fill() {
      System.out.println("Inside Blue::fill() method.");
   }
}

## 步骤 5ColorShape 对象创建抽象类来获取工厂。
AbstractFactory.java
public abstract class AbstractFactory {
   public abstract Color getColor(String color);
   public abstract Shape getShape(String shape) ;
}

## 步骤 6 创建扩展了AbstractFactory 的工厂类,基于给定的信息生成实体类的对象(工厂类)ShapeFactory.java  //形状的工厂类
public class ShapeFactory extends AbstractFactory {
   @Override
   public Shape getShape(String shapeType){
      if(shapeType == null){
         return null;
      }        
      if(shapeType.equalsIgnoreCase("CIRCLE")){
         return new Circle();
      } else if(shapeType.equalsIgnoreCase("RECTANGLE")){
         return new Rectangle();
      } else if(shapeType.equalsIgnoreCase("SQUARE")){
         return new Square();
      }
      return null;
   }
   @Override
   public Color getColor(String color) {
      return null;
   }
}

ColorFactory.java //颜色工厂类
public class ColorFactory extends AbstractFactory {
   @Override
   public Shape getShape(String shapeType){
      return null;
   }
   @Override
   public Color getColor(String color) {
      if(color == null){
         return null;
      }        
      if(color.equalsIgnoreCase("RED")){
         return new Red();
      } else if(color.equalsIgnoreCase("GREEN")){
         return new Green();
      } else if(color.equalsIgnoreCase("BLUE")){
         return new Blue();
      }
      return null;
   }
}

## 步骤 7 创建一个工厂创造器/生成器类,通过传递形状或颜色信息来获取工厂。
FactoryProducer.java
public class FactoryProducer {
   public static AbstractFactory getFactory(String choice){
      if(choice.equalsIgnoreCase("SHAPE")){
         return new ShapeFactory();
      } else if(choice.equalsIgnoreCase("COLOR")){
         return new ColorFactory();
      }
      return null;
   }
}

## 步骤 8 使用FactoryProducer来获取 AbstractFactory,通过传递类型信息来获取实体类的对象。
AbstractFactoryPatternDemo.java
public class AbstractFactoryPatternDemo {
   public static void main(String[] args) {
      //获取形状工厂
      AbstractFactory shapeFactory = FactoryProducer.getFactory("SHAPE");
 
      //获取形状为 Circle 的对象
      Shape shape1 = shapeFactory.getShape("CIRCLE");
 
      //调用 Circle 的 draw 方法
      shape1.draw();
 
      //获取形状为 Rectangle 的对象
      Shape shape2 = shapeFactory.getShape("RECTANGLE");
 
      //调用 Rectangle 的 draw 方法
      shape2.draw();
      
      //获取形状为 Square 的对象
      Shape shape3 = shapeFactory.getShape("SQUARE");
 
      //调用 Square 的 draw 方法
      shape3.draw();
 
      //获取颜色工厂
      AbstractFactory colorFactory = FactoryProducer.getFactory("COLOR");
 
      //获取颜色为 Red 的对象
      Color color1 = colorFactory.getColor("RED");
 
      //调用 Red 的 fill 方法
      color1.fill();
 
      //获取颜色为 Green 的对象
      Color color2 = colorFactory.getColor("Green");
 
      //调用 Green 的 fill 方法
      color2.fill();
 
      //获取颜色为 Blue 的对象
      Color color3 = colorFactory.getColor("BLUE");
 
      //调用 Blue 的 fill 方法
      color3.fill();
   }
}
## 步骤 9  执行程序,输出结果:
Inside Circle::draw() method.
Inside Rectangle::draw() method.
Inside Square::draw() method.
Inside Red::fill() method.
Inside Green::fill() method.
Inside Blue::fill() method.

观察者模式

当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式。

  1. 观察者模式(Observer),当一个对象状态发生变化时,依赖它的对象全部会收到通知,并自动更新。
  2. 场景:一 个事件发生后,要执行一连串更新操作。传统的编程方式,就是在事件的代码之后直接加入处理的逻辑。当更新的逻 辑增多之后,代码会变得难以维护。这种方式是耦合的,侵入式的,增加新的逻辑需要修改事件的主体代码。
  3. 观 察者模式实现了低耦合,非侵入式的通知与更新机制。 定义一个事件触发抽象类。

应用实例:

  • 拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。
  • 西游记里面悟空请求菩萨降服红孩儿,菩萨洒了一地水招来一个老乌龟,这个乌龟就是观察者,他观察菩萨洒水这个动作。

优点

  • 观察者和被观察者是抽象耦合的。
  • 建立一套触发机制。

缺点

  • 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
  • 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
  • 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

使用场景

  • 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
  • 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
  • 一个对象必须通知其他对象,而并不知道这些对象是谁。
  • 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。

注意事项

  • JAVA 中已经有了对观察者模式的支持类。
  • 避免循环引用。
  • 如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。

实现

观察者模式使用三个类 Subject、Observer 和 Client。Subject 对象带有绑定观察者到 Client 对象和从 Client 对象解绑观察者的方法。我们创建 Subject 类、Observer 抽象类和扩展了抽象类 Observer 的实体类。

ObserverPatternDemo,我们的演示类使用 Subject 和实体类对象来演示观察者模式。

观察者模式的 UML 图

## 步骤 1 创建 Subject 类。
Subject.java
import java.util.ArrayList;
import java.util.List;
 
public class Subject {
   private List<Observer> observers 
      = new ArrayList<Observer>();
   private int state;
 
   public int getState() {
      return state;
   }
 
   public void setState(int state) {
      this.state = state;
      notifyAllObservers();
   }
 
   public void attach(Observer observer){
      observers.add(observer);      
   }
 
   public void notifyAllObservers(){
      for (Observer observer : observers) {
         observer.update();
      }
   }  
}

## 步骤 2 创建 Observer 类。
Observer.java
public abstract class Observer {
   protected Subject subject;
   public abstract void update();
}

## 步骤 3 创建实体观察者类。
BinaryObserver.java
public class BinaryObserver extends Observer{
 
   public BinaryObserver(Subject subject){
      this.subject = subject;
      this.subject.attach(this);
   }
 
   @Override
   public void update() {
      System.out.println( "Binary String: " 
      + Integer.toBinaryString( subject.getState() ) ); 
   }
}
OctalObserver.java
public class OctalObserver extends Observer{
 
   public OctalObserver(Subject subject){
      this.subject = subject;
      this.subject.attach(this);
   }
 
   @Override
   public void update() {
     System.out.println( "Octal String: " 
     + Integer.toOctalString( subject.getState() ) ); 
   }
}
HexaObserver.java
public class HexaObserver extends Observer{
 
   public HexaObserver(Subject subject){
      this.subject = subject;
      this.subject.attach(this);
   }
 
   @Override
   public void update() {
      System.out.println( "Hex String: " 
      + Integer.toHexString( subject.getState() ).toUpperCase() ); 
   }
}

## 步骤 4 使用 Subject 和实体观察者对象。
ObserverPatternDemo.java
public class ObserverPatternDemo {
   public static void main(String[] args) {
      Subject subject = new Subject();
 
      new HexaObserver(subject);
      new OctalObserver(subject);
      new BinaryObserver(subject);
 
      System.out.println("First state change: 15");   
      subject.setState(15);
      System.out.println("Second state change: 10");  
      subject.setState(10);
   }
}

## 步骤 5 执行程序,输出结果:
First state change: 15
Hex String: F
Octal String: 17
Binary String: 1111
Second state change: 10
Hex String: A
Octal String: 12
Binary String: 1010

适配器模式

适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。

这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。举个真实的例子,读卡器是作为内存卡和笔记本之间的适配器。您将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡。

将各种截然不同的函数接口封装成统一的API

PHP中的数据库操作有MySQL,MySQLi,PDO三种,可以用适配器模式 统一成一致,使不同的数据库操作,统一成一样的API。类似的场景还有cache适配器,可以将 memcache,redis,file,apc等不同的缓存函数,统一成一致。

首先定义一个接口(有几个方法,以及相应的参数)。然 后,有几种不同的情况,就写几个类实现该接口。将完成相似功能的函数,统一成一致的方法。

优点

  • 可以让任何两个没有关联的类一起运行。
  • 提高了类的复用。
  • 增加了类的透明度。
  • 灵活性好。

缺点

  • 过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。

实现

我们有一个 MediaPlayer 接口和一个实现了 MediaPlayer 接口的实体类 AudioPlayer。默认情况下,AudioPlayer 可以播放 mp3 格式的音频文件。

我们还有另一个接口 AdvancedMediaPlayer 和实现了 AdvancedMediaPlayer 接口的实体类。该类可以播放 vlc 和 mp4 格式的文件。

我们想要让 AudioPlayer 播放其他格式的音频文件。为了实现这个功能,我们需要创建一个实现了 MediaPlayer 接口的适配器类 MediaAdapter,并使用 AdvancedMediaPlayer 对象来播放所需的格式。

AudioPlayer 使用适配器类 MediaAdapter 传递所需的音频类型,不需要知道能播放所需格式音频的实际类。AdapterPatternDemo 类使用 AudioPlayer 类来播放各种格式。

适配器模式的 UML 图

## 步骤 1 为媒体播放器和更高级的媒体播放器创建接口。
MediaPlayer.java
public interface MediaPlayer {
   public void play(String audioType, String fileName);
}
AdvancedMediaPlayer.java
public interface AdvancedMediaPlayer { 
   public void playVlc(String fileName);
   public void playMp4(String fileName);
}

## 步骤 2 创建实现了 AdvancedMediaPlayer 接口的实体类。
VlcPlayer.java
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) {
      //什么也不做
   }
}
Mp4Player.java
public class Mp4Player implements AdvancedMediaPlayer{
 
   @Override
   public void playVlc(String fileName) {
      //什么也不做
   }
 
   @Override
   public void playMp4(String fileName) {
      System.out.println("Playing mp4 file. Name: "+ fileName);      
   }
}

## 步骤 3 创建实现了 MediaPlayer 接口的适配器类。
MediaAdapter.java
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);
      }
   }
}

## 步骤 4 创建实现了 MediaPlayer 接口的实体类。
AudioPlayer.java
public class AudioPlayer implements MediaPlayer {
   MediaAdapter mediaAdapter; 
 
   @Override
   public void play(String audioType, String fileName) {    
 
      //播放 mp3 音乐文件的内置支持
      if(audioType.equalsIgnoreCase("mp3")){
         System.out.println("Playing mp3 file. Name: "+ fileName);         
      } 
      //mediaAdapter 提供了播放其他文件格式的支持
      else if(audioType.equalsIgnoreCase("vlc") 
         || audioType.equalsIgnoreCase("mp4")){
         mediaAdapter = new MediaAdapter(audioType);
         mediaAdapter.play(audioType, fileName);
      }
      else{
         System.out.println("Invalid media. "+
            audioType + " format not supported");
      }
   }   
}

## 步骤 5 使用 AudioPlayer 来播放不同类型的音频格式。
AdapterPatternDemo.java
public class AdapterPatternDemo {
   public static void main(String[] args) {
      AudioPlayer audioPlayer = new AudioPlayer();
 
      audioPlayer.play("mp3", "beyond the horizon.mp3");
      audioPlayer.play("mp4", "alone.mp4");
      audioPlayer.play("vlc", "far far away.vlc");
      audioPlayer.play("avi", "mind me.avi");
   }
}

## 步骤 6 执行程序,输出结果:
Playing mp3 file. Name: beyond the horizon.mp3
Playing mp4 file. Name: alone.mp4
Playing vlc file. Name: far far away.vlc
Invalid media. avi format not supported

策略模式

策略模式是对象的行为模式,**用意是对一组算法的封装。动态的选择需要的算法并使用。**在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。

在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。

策略模式的三个角色:

  1. 抽象策略角色
  2. 具体策略角色
  3. 环境角色(对抽象策略角色的引用)

应用实例

  • 诸葛亮的锦囊妙计,每一个锦囊就是一个策略。
  • 旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策
  • JAVA AWT 中的 LayoutManager。

优点

  • 算法可以自由切换。
  • 避免使用多重条件判断。
  • 扩展性良好。

缺点

  • 策略类会增多。
  • 所有策略类都需要对外暴露。

使用场景

  • 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
  • 一个系统需要动态地在几种算法中选择一种。
  • 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。

实现步骤

  1. 定义抽象角色类(定义好各个实现的共同抽象方法)
  2. 定义具体策略类(具体实现父类的共同方法)
  3. 定义环境角色类(私有化申明抽象角色变量,重载构造方法,执行抽象方法)

实现

我们将创建一个定义活动的 Strategy 接口和实现了 Strategy 接口的实体策略类。Context 是一个使用了某种策略的类。

StrategyPatternDemo,我们的演示类使用 Context 和策略对象来演示 Context 在它所配置或使用的策略改变时的行为变化。

策略模式的 UML 图

## 步骤 1 创建一个接口。
Strategy.java
public interface Strategy {
   public int doOperation(int num1, int num2);
}

## 步骤 2 创建实现接口的实体类。
OperationAdd.java
public class OperationAdd implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 + num2;
   }
}
OperationSubtract.java
public class OperationSubtract implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 - num2;
   }
}
OperationMultiply.java
public class OperationMultiply implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 * num2;
   }
}

## 步骤 3 创建 Context 类。
Context.java
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);
   }
}

## 步骤 4 使用Context来查看当它改变策略 Strategy 时的行为变化。
StrategyPatternDemo.java
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 OperationSubtract());      
      System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
 
      context = new Context(new OperationMultiply());    
      System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
   }
}

## 步骤 5 执行程序,输出结果:
10 + 5 = 15
10 - 5 = 5
10 * 5 = 50

注册模式

注册模式,解决全局共享和交换对象。已经创建好的对象,挂在到某个全局可以使用的数组上,在需要使用的时候,直接从该数组上获取即可。将对象注册到全局的树上。任何地方直接去访问。

门面模式 face

  • 13
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

baiyyxx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值