Java 常见的9种设计模式(二)

命令模式、策略模式、门面模式、桥接模式、观察者模式

Posted by LANY on April 10, 2019

Java常见的9种设计模式(二)

我们在上一章Java常见的9种设计模式(一)中认识了单例模式简单工厂模式工厂模式代理模式这四种设计模式,今天继续来认识剩余的五种常见设计模式: 命令模式策略模式门面模式桥接模式观察者模式

命令模式

我们在日常的开发中,经常会根据业务逻辑进行一定的功能开发,但是有时候在完成一个功能开发时,会发现该功能的步骤充满了一些不确定性,只有当执行该方法时才能确定这个步骤如何完成,那么我们可以通过设计模式去规避这样的不确定性。

伪代码如下:

创建Command接口,该接口我们可以把它称为命令接口

public interface Command {
    void process(int[] target);
}

创建ProcessArray类,该类用于完成对数组的处理

/**
 * Created by lany on 2018/9/11.
 */
public class ProcessArray {

    //定义一个each()方法,用于处理数组。
    public void each(int[] target,Command cmd){
        cmd.process(target);
    }
}

创建TestCommand

/**
 * Created by lany on 2018/9/11.
 */
public class TestCommand {
    public static void main(String[] args){
        ProcessArray processArray = new ProcessArray();
        int[] target = {-1,2,3,5};
        //实现处理过程
        processArray.each(target, new Command() {
            int sum = 0;
            @Override
            public void process(int[] target) {
                //计算数组的和
                for (int tar: target
                     ) {
                    sum += tar;
                }
                System.out.println(sum);
            }
        });
    }
}

在上述伪代码中,传入该方法的是一个对象,该对象通常是某个接口的匿名实现类的实例,该接口通常被称为命令接口,这种设计方式也被称为命令模式。

HibernateTemplate使用了executeXxx()方法弥补了HibernateTemplate的不足,该方法需要接受一个HibernateCallback接口

策略模式

策略模式用于封装系=系统中的算法,这些算法通常被封装在一个被称为Context的类中,客户端程序可以自由选择其中一种算法,或让Context为客户端选择一种最佳方法–使用策略模式的优势是为了支持算法的自由切换。

伪代码如下:

创建Operation接口,抽象成操作策略接口

/**
 * 策略接口
 * Created by lany on 2018/9/3.
 */
public interface Operation {

    float parameter(float a,float b);

}

创建Addtion类,该类为策略接口的实现类,操作策略为加法

/**
 * 加法具体策略类
 * Created by lany on 2018/9/3.
 */
public class Addtion implements Operation {

    @Override
    public float parameter(float a, float b) {
        return a+b;
    }
}

创建Multiplication类,该类为策略接口的实现类,操作策略为乘法

/**
 * 乘法具体策略类
 * Created by lany on 2018/9/3.
 */
public class Multiplication implements Operation {

    @Override
    public float parameter(float a, float b) {
        return a*b;
    }
}

创建OperationContext 类,用于定义具体的策略

**
 * 操作策略定义
 * Created by lany on 2019/4/10.
 */
public class OperationContext {

    private Operation operation;

    public OperationContext() {
    }

    //传入某种操作的策略接口
    public OperationContext(Operation operation) {
        this.operation = operation;
    }


    //根据传入的操作策略对数值进行计算
    public float calculate(float a , float b){
        //如果没有对操作策略进行限定,那么系统默认使用加法操作策略
        if(null == operation){
            this.operation = new Addtion();
        }
        return this.operation.parameter(a,b);
    }

    public void setOperation(Operation operation){
        this.operation = operation;
    }

	//测试
    public static void main(String[] args){
        //当没有对操作策略进行定义时,默认使用加法操作策略
        OperationContext operationContext = new OperationContext();
        System.out.println(operationContext.calculate(3,5));
        //定义操作策略为乘法策略
        operationContext.setOperation(new Multiplication());
        System.out.println(operationContext.calculate(3,5));
    }
}

运行上述伪代码,我们可以发现采用不同的策略,返回的结果就不同

8.0
15.0

使用该策略的优点是我们可以在不同的策略中进行切换,但是这增加了客户端跟不同策略的耦合程度。我们可以考虑使用配置文件来指定具体的操作方法。

门面模式

门面模式理解起来很简单,其实就是将一组复杂的类封装到一个简单的外部接口中。

比如说,我们实现某一个功能需要创建不同的类然后分别调用各个类的方法,为了简化该功能的调用,我们可以把创建以及调用等步骤封装到一个方法中,下次实现该功能,只需要调用封装好的这个方法即可。

伪代码如下:

原来的方式调用:

 // 依次创建三个部门实例
 Payment pay = new PaymentImpl();
 Cook cook = new CookImpl();
 Waiter waiter = new WaiterImpl();
 // 依次调用三个部门实例的方法来实现用餐功能
 String food = pay.pay();
 food = cook.cook(food);
 waiter.serve(food);

使用门面模式封装

public class Facade {
    // 定义被Facade封装的三个部门
    Payment pay;
    Cook cook;
    Waiter waiter;
 
    // 构造器
    public Facade() {
       this.pay = new PaymentImpl();
       this.cook = new CookImpl();
       this.waiter = new WaiterImpl();
    }
 
    publicvoid serveFood() {
       // 依次调用三个部门的方法,封装成一个serveFood()方法
       String food = pay.pay();
       food = cook.cook(food);
       waiter.serve(food);
    }
}

调用封装后的方法

  Facade f = new Facade();
  f.serveFood();

桥接模式

由于实际的需要,某个类具有两个以上的维度变化,如果只是使用继承将无法实现这种需要,或者使得设计变得相当臃肿。而桥接模式是把变化的部分抽象出来,使变化部分与主类分离开来,从而将多个的变化彻底分离,从而将多个变化彻底分离。最后提供一个管理类来组合不同维度上的变化。

创建Peppery 接口,定义一个风味接口

/**
 * peppery风味接口
 * Created by lany on 2018/9/10.
 */
public interface Peppery {
    String style();
}

创建PlainStyle类,实现风味接口,该风味为不辣

/**
 *
 * 实现不辣风格的口味
 * Created by lany on 2018/9/10.
 */
public class PlainStyle implements Peppery{
    @Override
    public String style() {
        return "重口味者慎入,养生党看过来.";
    }
}

创建PepperyStyle 类,实现风味接口,该风味为超级辣

/**
 * 实现超级辣口味的风格
 * Created by lany on 2018/9/10.
 */
public class PepperyStyle implements Peppery{
    @Override
    public String style() {
        return "味道非常辣,非重口味者勿入!";
    }
}

创建AbstractNoodle 抽象类,该类为面条的口味的桥梁

/**
 *
 * 口味的桥梁
 * Created by lany on 2018/9/10.
 */
public abstract class AbstractNoodle {

    //组合一个peppery变量,用于将该维度的变化独立出来
    protected Peppery style;

    //每份noddle必须组合一个peppery对象
    public AbstractNoodle(Peppery style){
        this.style = style;
    }

    public abstract void eat();

}

创建PorkyNoodle类,该类定义为猪肉面的口味介绍

public class PorkyNoodle extends AbstractNoodle{

    public PorkyNoodle(Peppery style) {
        super(style);
    }

    @Override
    public void eat() {
        System.out.println("这是一份很油腻的猪肉面条"
        +super.style.style());
    }
}

创建BeefNoodle类,该类定义为牛肉面的口味介绍

public class BeefNoodle extends AbstractNoodle {

    public BeefNoodle(Peppery style) {
        super(style);
    }

    @Override
    public void eat() {
        System.out.println("这是一份鲜美的牛肉面"+super.style.style());
    }
}

测试

public class Test {
    public static void main(String[] args){
        //一份超辣的美味牛肉面
        AbstractNoodle abstractNoodle = new BeefNoodle(new PepperyStyle()) ;
        abstractNoodle.eat();
    }
}

其实在Java的dao层也是使用的该桥接模式。

观察者模式

观察者模式定义了对象间的一对多依赖关系,让一个或多个观察者对象观察一个主题对象。当主题对象的状态发生变化时,系统能通知所有的依赖于此对象的观察者对象,从而使得观察者对象能够自动更新。

在观察者模式中,被观察的对象常常也被称为目标或主题(Subject),依赖的对象被称为观察者(Observer)。

观察者:观察者也是一个接口,该接口规定了具体观察者用来更新数据的方法,伪代码如下:

创建Observer 接口,该接口用来规定了具体观察者用来更新的方法

**
 * 观察者接口,该接口用来规定了具体观察者用来更新的方法
 * Created by lany on 2018/9/10.
 */
public interface Observer {

    /**
     * 定义用来规定具体观察着用来更新的方法
     * @param o
     * @param arg
     */
    void update(Observable o,Object arg);

}

具体观察者:具体观察者是实现了观察者接口的一个类。具体观察者包含有可以存放具体主题引用的主题接口变量,以便具体观察者让具体主题将自己的引用添加到具体主题的集合中,让自己成为它的观察者,或者让这个具体主题将自己从具体主题的集合中删除,使自己不在时它的观察者。伪代码如下:

创建NameObserver 类,该类为观察者接口的实现类

/**
 * 具体观察者实现了观察者接口的一个类.具体观察者包含有可以存放具体主题引用的主题接口变量
 * Created by lany on 2018/9/10.
 */
public class NameObserver implements Observer{

    @Override
    public void update(Observable o, Object arg) {

        if(arg instanceof String){
            String name = (String)arg;
            JFrame f = new JFrame("观察者");
            JLabel l = new JLabel("名称改变为:"+ name);
            f.add(l);
            f.pack();
            f.setVisible(true);
            System.out.println("观察者名称:"+o+"物品名称已经改变为:"+name);
        }

    }
}

主题:主题是一个接口,该接口规定了具体主题需要实现的方法,比如添加、删除观察者以及通知观察者更新数据的方法.伪代码如下:

创建Observable 抽象类

/**
 * 目标或者主题
 * 主题是一个抽象类,该接口规定了需要实现的方法,比如添加,删除观察者以及通知观察者更新数据的方法
 * Created by lany on 2018/9/10.
 */
public abstract class Observable {

    List<Observer> observers = new ArrayList<Observer>();

    /**
     * 注册观察者
     * @param observer
     */
    public void registObserver(Observer observer){
        observers.add(observer);
    }

    /**
     * 注销观察者
     * @param observer
     */
    public void removeObserver(Observer observer){
//        observer.
    }
    /**
     * 通知该主题上注册的所有观察者
     * @param value
     */
    public void notifyObservers(Object value){
        for(Observer observer :observers){
            observer.update(this,value);
        }
    }
}

具体主题:具体主题是一个实现主题接口的类,该类包含了会经常发生变化的数据。而且还有一个集合,该集合存放的是观察者的引用。伪代码如下:

创建Product类,该类为被观察者类

/**
 * 具体被观察者类
 * Created by lany on 2018/9/10.
 */
public class Product extends Observable{

    private String name;

    private double price;


    public Product(){}

    public Product(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
        notifyObservers(name);
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
        notifyObservers(price);
    }
}

测试

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

        Product product = new Product("电视机",2999.99);

        NameObserver no = new NameObserver();

        product.registObserver(no);

        product.setName("电脑");

        System.out.println(product.getName());

    }
}

通过上述伪代码,我们可以实现当产品的名称被改变之后,把改变的信息通知到所有观察者。