Try   HackMD

工厂方法通过定义中间层,只能将两个模块解耦,当一个业务需要至少三个相互依赖的模块互相调用才能运转时,就需要在这些模块之间定义中间人模块,这种设计就是中间人模式,也叫做中介者模式。

以进销存这样的常见业务为例:

  • 销售部门要反馈销售情况,畅销就催促供应链多采购,滞销就不采购。
  • 仓储管理部门要反馈库存情况,再畅销的产品,有货才能销售;如果库存太多要通知采购部门控制采购量,以及通知销售部门采取措施清库存。
  • 供应链管理部门除了配合销售和仓储外,也要反馈上游供货信息,让销售和仓储部门指定自己的规划。

这样的业务模式就形成了相互依赖,如图:

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

在实现业务代码时,几个类之间也是牵一发而动全身:

@Deprecated
class Purchase {
    // 采购IBM电脑
    public void buyIBMcomputer(int number) {
        // 访问库存
        Stock stock = new Stock();
        // 访问销售
        Sale sale = new Sale();
        // 电脑的销售情况
        int saleStatus = sale.getSaleStatus();
        if (saleStatus > 80) { // 销售情况良好
            System.out.println("采购IBM电脑:" + number + "台");
            stock.increase(number); // 全额采购
        } else { // 销售情况不好
            int buyNumber = number / 2; // 折半采购
            System.out.println("采购IBM电脑:" + buyNumber + "台");
        }
    }

    // 不再采购IBM电脑
    public void refuseBuyIBM() {
        System.out.println("不再采购IBM电脑");
    }
}

@Deprecated
class Stock {
    // 刚开始有100台电脑
    private static int COMPUTER_NUMBER = 100;

    // 库存增加
    public void increase(int number) {
        COMPUTER_NUMBER = COMPUTER_NUMBER + number;
        System.out.println("库存数量为:" + COMPUTER_NUMBER);
    }

    // 库存降低
    public void decrease(int number) {
        COMPUTER_NUMBER = COMPUTER_NUMBER - number;
        System.out.println("库存数量为:" + COMPUTER_NUMBER);
    }

    // 获得库存数量
    public int getStockNumber() {
        return COMPUTER_NUMBER;
    }

    // 存货压力大了,就要通知采购人员不要采购,销售人员要尽快销售
    public void clearStock() {
        Purchase purchase = new Purchase();
        Sale sale = new Sale();
        System.out.println("清理存货数量为:" + COMPUTER_NUMBER);
        // 要求折价销售
        sale.offSale();
        // 要求采购人员不要采购
        purchase.refuseBuyIBM();
    }
}

@Deprecated
class Sale {
    // 销售IBM电脑
    public void sellIBMComputer(int number) {
        // 访问库存
        Stock stock = new Stock();
        // 访问采购
        Purchase purchase = new Purchase();
        if (stock.getStockNumber() < number) { // 库存数量不够销售
            purchase.buyIBMcomputer(number);
        }
        System.out.println("销售 IBM 电脑" + number + "台");
        stock.decrease(number);
    }

    // 反馈销售情况,0~100之间变化,0代表根本就没人卖,100代表非常畅销,出一个卖一个
    public int getSaleStatus() {
        Random rand = new Random(System.currentTimeMillis());
        int saleStatus = rand.nextInt(100);
        System.out.println("IBM 电脑的销售情况为:" + saleStatus);
        return saleStatus;
    }

    // 折价处理
    public void offSale() {
        // 库房有多少卖多少
        Stock stock = new Stock();
        System.out.println("折价销售 IBM 电脑" + stock.getStockNumber() + "台");
    }
}

public class App {
    public static void main(String[] args) {
        // 采购人员采购电脑
        System.out.println("------采购人员采购电脑--------");
        Purchase purchase = new Purchase();
        purchase.buyIBMcomputer(100);
        // 销售人员销售电脑
        System.out.println("\n------销售人员销售电脑--------");
        Sale sale = new Sale();
        sale.sellIBMComputer(1);
        // 库房管理人员管理库存
        System.out.println("\n------库房管理人员清库处理--------");
        Stock stock = new Stock();
        stock.clearStock();
    }
}

中间人模式则是引入一个中间人模块作为其余模块的交流中心,每个模块只负责自己的业务逻辑,模块之间不再相互交流,要交流就通过中间人进行,从而简化了各模块之间的耦合关系:

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

使用中间人模式解耦后的类图如下:

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

在接口层面存在两个层级:AbstractMediator 和 AbstractColleague:

  • AbstractMediator 是中间人模块的抽象定义,业务中不同模块耦合的部分在子类中实现,也可以可以根据业务的要求产生多个中介者,并划分各中介者的职责。具体中介者如下:
    ​​​​abstract class AbstractMediator {
    ​​​​    protected PurchaseImpl purchase;
    ​​​​    protected SaleImpl sale;
    ​​​​    protected StockImpl stock;
    
    ​​​​    public void setPurchase(PurchaseImpl purchase) {
    ​​​​        this.purchase = purchase;
    ​​​​    }
    
    ​​​​    public void setSale(SaleImpl sale) {
    ​​​​        this.sale = sale;
    ​​​​    }
    
    ​​​​    public void setStock(StockImpl stock) {
    ​​​​        this.stock = stock;
    ​​​​    }
    
    ​​​​    public PurchaseImpl getPurchase() {
    ​​​​        return purchase;
    ​​​​    }
    
    ​​​​    public SaleImpl getSale() {
    ​​​​        return sale;
    ​​​​    }
    
    ​​​​    public StockImpl getStock() {
    ​​​​        return stock;
    ​​​​    }
    
    ​​​​    // 中介者最重要的方法叫做事件方法,处理多个对象之间的关系
    ​​​​    public abstract void execute(String str, Object... objects);
    ​​​​}
    
    ​​​​class Mediator extends AbstractMediator {
    ​​​​    // 中介者最重要的方法
    ​​​​    public void execute(String str, Object... objects) {
    ​​​​        if (str.equals("purchase.buy")) { // 采购电脑
    ​​​​            this.buyComputer((Integer) objects[0]);
    ​​​​        } else if (str.equals("sale.sell")) { // 销售电脑
    ​​​​            this.sellComputer((Integer) objects[0]);
    ​​​​        } else if (str.equals("sale.offsell")) { // 折价销售
    ​​​​            this.offSell();
    ​​​​        } else if (str.equals("stock.clear")) { // 清仓处理
    ​​​​            this.clearStock();
    ​​​​        }
    ​​​​    }
    
    ​​​​    // 采购电脑
    ​​​​    private void buyComputer(int number) {
    ​​​​        int saleStatus = super.sale.getSaleStatus();
    ​​​​        if (saleStatus > 80) { // 销售情况良好
    ​​​​            System.out.println("采购IBM电脑:" + number + "台");
    ​​​​            super.stock.increase(number);
    ​​​​        } else { // 销售情况不好
    ​​​​            int buyNumber = number / 2; // 折半采购
    ​​​​            System.out.println("采购IBM电脑:" + buyNumber + "台");
    ​​​​        }
    ​​​​    }
    
    ​​​​    // 销售电脑
    ​​​​    private void sellComputer(int number) {
    ​​​​        if (super.stock.getStockNumber() < number) { // 库存数量不够销售
    ​​​​            super.purchase.buyIBMcomputer(number);
    ​​​​        }
    ​​​​        super.stock.decrease(number);
    ​​​​    }
    
    ​​​​    // 折价销售电脑
    ​​​​    private void offSell() {
    ​​​​        System.out.println("折价销售IBM电脑" + stock.getStockNumber() + "台");
    ​​​​    }
    
    ​​​​    // 清仓处理
    ​​​​    private void clearStock() {
    ​​​​        // 要求清仓销售
    ​​​​        super.sale.offSale();
    ​​​​        // 要求采购人员不要采购
    ​​​​        super.purchase.refuseBuyIBM();
    ​​​​    }
    ​​​​}
    
  • 业务模块中直接和 Mediator 通信,因为都依赖 Mediator ,所以将 Mediator 的注入提取到父类中:
    ​​​​abstract class AbstractColleague {
    ​​​​    protected AbstractMediator mediator;
    
    ​​​​    public AbstractColleague(AbstractMediator _mediator) {
    ​​​​        this.mediator = _mediator;
    ​​​​    }
    ​​​​}
    
    ​​​​class PurchaseImpl extends AbstractColleague {
    ​​​​    public PurchaseImpl(AbstractMediator _mediator) {
    ​​​​        super(_mediator);
    ​​​​    }
    
    ​​​​    // 采购 IBM 电脑
    ​​​​    public void buyIBMcomputer(int number) {
    ​​​​        super.mediator.execute("purchase.buy", number);
    ​​​​    }
    
    ​​​​    // 不再采购 IBM 电脑
    ​​​​    public void refuseBuyIBM() {
    ​​​​        System.out.println("不再采购IBM电脑");
    ​​​​    }
    ​​​​}
    
    ​​​​class StockImpl extends AbstractColleague {
    ​​​​    public StockImpl(AbstractMediator _mediator) {
    ​​​​        super(_mediator);
    ​​​​    }
    
    ​​​​    // 刚开始有 100 台电脑
    ​​​​    private static int COMPUTER_NUMBER = 100;
    
    ​​​​    // 库存增加
    ​​​​    public void increase(int number) {
    ​​​​        COMPUTER_NUMBER = COMPUTER_NUMBER + number;
    ​​​​        System.out.println("库存数量为:" + COMPUTER_NUMBER);
    ​​​​    }
    
    ​​​​    // 库存降低
    ​​​​    public void decrease(int number) {
    ​​​​        COMPUTER_NUMBER = COMPUTER_NUMBER - number;
    ​​​​        System.out.println("库存数量为:" + COMPUTER_NUMBER);
    ​​​​    }
    
    ​​​​    // 获得库存数量
    ​​​​    public int getStockNumber() {
    ​​​​        return COMPUTER_NUMBER;
    ​​​​    }
    
    ​​​​    // 存货压力大了,就要通知采购人员不要采购,销售人员要尽快销售
    ​​​​    public void clearStock() {
    ​​​​        System.out.println("清理存货数量为:" + COMPUTER_NUMBER);
    ​​​​        super.mediator.execute("stock.clear");
    ​​​​    }
    ​​​​}
    
    ​​​​class SaleImpl extends AbstractColleague {
    ​​​​    public SaleImpl(AbstractMediator _mediator) {
    ​​​​        super(_mediator);
    ​​​​    }
    
    ​​​​    // 销售 IBM 电脑
    ​​​​    public void sellIBMComputer(int number) {
    ​​​​        super.mediator.execute("sale.sell", number);
    ​​​​        System.out.println("销售IBM电脑" + number + "台");
    ​​​​    }
    
    ​​​​    // 反馈销售情况,0~100 变化,0 代表根本就没人买,100 代表非常畅销,出一个卖一个
    ​​​​    public int getSaleStatus() {
    ​​​​        Random rand = new Random(System.currentTimeMillis());
    ​​​​        int saleStatus = rand.nextInt(100);
    ​​​​        System.out.println("IBM电脑的销售情况为:" + saleStatus);
    ​​​​        return saleStatus;
    ​​​​    }
    
    ​​​​    // 折价处理
    ​​​​    public void offSale() {
    ​​​​        super.mediator.execute("sale.offsell");
    ​​​​    }
    ​​​​}
    
  • 注意:同事类要使用构造函数注入中介者,而中介者使用getter/setter方式注入同事类,这是因为同事类必须有中介者,而中介者却可以只有部分同事类。

修改后的场景类如下

public class Client {
    public static void main(String[] args) {
        AbstractMediator mediator = new Mediator();
        PurchaseImpl purchaseImpl = new PurchaseImpl(mediator);
        SaleImpl saleImpl = new SaleImpl(mediator);
        StockImpl stockImpl = new StockImpl(mediator);
        mediator.setPurchase(purchaseImpl);
        mediator.setSale(saleImpl);
        mediator.setStock(stockImpl);
        //采购人员采购电脑
        System.out.println("------采购人员采购电脑--------");
        purchaseImpl.buyIBMcomputer(100);
        //销售人员销售电脑
        System.out.println("\n------销售人员销售电脑--------");
        saleImpl.sellIBMComputer(1);
        //库房管理人员管理库存
        System.out.println("\n------库房管理人员清库处理--------");
        stockImpl.clearStock();
    }
}

中间人模式的优点

显而易见,减少类间的依赖,把原有的一对多的依赖变成了一对一的依赖,同事类只依赖中介者,减少了依赖,当然同时也降低了类间的耦合。

中间人模式的缺点

中间人层会因业务模块的增加变得越加复杂

中间人模式的使用场景

中间人模式简单,但是简单不代表容易使用,很容易被误用。在面向对象的编程中,对象和对象之间必然会有依赖关系,如果某个类和其他类没有任何相互依赖的关系,那这个类就是一个“孤岛”,在项目中就没有存在的必要了!类之间的依赖关系是必然存在的,一个类依赖多个类的情况也是存在的,存在即合理,那是否可以说只要有多个依赖关系就考虑使用中间人模式呢?答案是否定的。中间人模式未必能帮你把原本凌乱的逻辑整理得清清楚楚,而且中间人模式也是有缺点的,这个缺点在使用不当时会被放大,比如原本就简单的几个对象依赖关系,如果为了使用模式而加入了中间人,必然导致中间人的逻辑复杂化,因此中间人模式的使用需要“量力而行”!中间人模式适用于多个对象之间紧密耦合的情况,紧密耦合的标准是:在类图中出现了蜘蛛网状结构。在这种情况下一定要考虑使用中间人模式,这有利于把蜘蛛网梳理为星型结构,使原本复杂混乱的关系变得清晰简单。

最后,中间人模式很少使用接口或抽象类,这与依赖倒置原则是冲突的,也是由于中间人模式的特点而决定:

  • 业务模块之间是平级协作的关系,中间人层并不关心业务模块之间的相同的部分,因此应避免在在抽象类或接口中严格定义业务模块的必须具有的方法(从这点也可以看出继承是高侵入性的)。
  • 中间人层中可能包含多个中间人模块,但每个中间人模块会围绕不同的业务模块,因此对于中间人模块来说,抽象已经没有太多必要。

中介者模式是一个非常好的封装模式,也是一个很容易被滥用的模式,一个对象依赖几个对象是再正常不过的事情,且使用中介模式就必然会带来中介者的膨胀问题,但是要求使用中介者模式来封装这种依赖关系,这在一个项目中是很不恰当的。当业务模块之间产生 N-N 依赖关系,且不确定不确定或者有发生改变的可能,在这种情况下一般建议采用中介者模式,降低变更引起的风险扩散,典型的就是产品开发,往往在迭代的过程中存在诸多不确定性,使用中间人模式能够将细分的模块组合成灵活的功能。与之相对的是项目开发,已项目验收为目标,设计相对固定,在明确了模块之间有 N-N 依赖,则使用中间人模式。