1. 程式人生 > 實用技巧 >Decorator 裝飾器模式 -動態組合

Decorator 裝飾器模式 -動態組合

>>> hot3.png

一:業務場景

獎金計算,每個部門,有不同的計算方法,且每個部門有不同型別的獎金項;而且每年或每隔幾個季度獎金演算法都要重新實現下。

這個應用場景應該比較合適了。

我先列出幾個計算公式:

每人當月的業務獎金 = 當月銷售額 * 3%;

每人累計獎金 = 總的回款額*0.1%;

團隊獎金 = 團隊銷售總額*1%;(只有經理才有哦)

二:不用設計模式,我們程式如下實現:

這裡演示程式碼,就不用資料庫了

/**
 * 在記憶體中模擬DB,準備好測試資料
 * @author dy
 * @since 2016-08-11  & JDK 1.8.0_91
 */
public class TempDB {

    /**
     * 記錄每人每月銷售額,這裡為了演示,只有人員,沒有月份
     * 這裡演示 假設每人每月銷售業務金額都是相同的哈
     */
    public static Map<String,Double> mapMonthSaleMoney = new HashMap<>();
    public static Map<String,String> saleManager = new HashMap<>();

    static {
        mapMonthSaleMoney.put("楊過", 10000.00);
        mapMonthSaleMoney.put("令狐沖", 20000.00);
        mapMonthSaleMoney.put("韋小寶", 30000.00);
        mapMonthSaleMoney.put("dy", 10000.00);

        saleManager.put("經理", "dy");
    }
}

接下來寫不同獎金計算方法

/**
 * 計算某人當月的業務獎金
 *
 * @param user
 * @param month
 * @return
 */
private double monthPrize(String user, int month) {

    //當月業務獎金 = 業務額*3%
    double prize = TempDB.mapMonthSaleMoney.get(user) * 0.03;
    System.out.println(user + " " + month + "月份業務獎金:" + prize);
    return prize;
}

/**
 * 計算某人當月累計獎金
 *
 * @param user
 * @param month
 * @return
 */
private double sumPrize(String user, int month) {

    //這裡演示 假設每人每月銷售業務金額都是相同的哈
    //累計業務獎金 = 累計業務金額*0.1%
    double prize = TempDB.mapMonthSaleMoney.get(user) * 0.001;
    System.out.println(user + " " +month+"月份累計獎金:"+prize);
    return prize;
}


/**
 * 是否是業務經理
 *
 * @param user
 * @return
 */
private boolean isManager(String user) {
    return TempDB.saleManager.get("經理").equals(user);
}

/**
 * 計算當月團隊獎金
 *
 * @param user
 * @param month
 * @return
 */
private double groupPrize(String user, int month) {
    if (!isManager(user)){
        return 0;
    }

    //團隊獎金 = 團隊總業務額 * 1%
    double prize = 0.00;
    for (double amount : TempDB.mapMonthSaleMoney.values()) {
        prize += amount;
    }
    prize = prize * 0.01;
    System.out.println(user + " " +month+"月份團隊獎金:"+prize);
    return prize;
}

ok,我們開始計算獎金吧

/**
 * 計算獎金
 * @param month
 * @return
 */
public void calcPrize(String user,int month) {

        double monthPrize = this.monthPrize(user, month);

        double sumPrize = this.sumPrize(user, month);

        double groupPrize =groupPrize(user, month);

        System.out.println(user + " " +month+"月份應得獎金:"+(monthPrize+sumPrize+groupPrize)+"==>>");

}

客戶端測試

public class Client {
    public static void main(String[] args) {
        Prize prize = new Prize();
        for (String user:TempDB.mapMonthSaleMoney.keySet()){
            prize.calcPrize(user,3);
        }
    }
}

測試結果:

以上程式碼有何問題??

如果我們計算獎金演算法變了呢??如果我們要增加新的獎金項? 如果我們要移除累計獎金項呢??等等

我們是不是需要頻繁改動程式碼呢?作為一個老鳥程式設計師,我們要求程式要更加靈活、支援可擴充套件!

如果做到呢?

我們把問題抽象下,假如有個獎金物件,可以支援靈活的增加或減少不同的獎金項,就意味著獎金物件的功能可以動態組合,這樣是不是會更好些呢?

三:使用裝飾模式,

動態地為一個物件增加或減少一些職責。就增加職責來說,比子類更加靈活。

如下是裝飾模式結構說明:

ok,讓我們利用裝飾模式,重新實現計算獎金吧!

/**
 * 計算金額的元件介面
 * @author dy
 * @since 2016-08-11  & JDK 1.8.0_91
 */
public abstract class Component {
    /**
     * 計算獎金
     * @param month
     */
    public abstract double calcPrize(String user,int month);
}
/**
 * 基本的實現類,預設實現,也是被裝飾的物件
 * @author dy
 * @since 2016-08-11  & JDK 1.8.0_91
 */
public class ConcreteComponent extends Component {
    @Override
    public double calcPrize(String user,int month) {
        System.out.println("預設沒有獎金!");
        return 0;
    }
}
/**
 * 裝飾器
 * 需要和被裝飾的物件實現同樣的介面
 * @author dy
 * @since 2016-08-11  & JDK 1.8.0_91
 */
public abstract class Decorator extends Component {

    /**
     * 持有被裝飾的物件
     */
    protected  Component component;

    public Decorator(Component component) {
        this.component = component;
    }

    @Override
    public double calcPrize(String user,int month) {
        double prize =  component.calcPrize(user,month);//轉調元件物件的方法
        return prize;
    }
}

/**
 * 裝飾器物件 ,計算當月業務獎金
 *
 * @author dy
 * @since 2016-08-11  & JDK 1.8.0_91
 */
public class MonthPrizeDecorator extends Decorator {
    public MonthPrizeDecorator(Component component) {
        super(component);
    }

    @Override
    public double calcPrize(String user, int month) {
        double money = super.calcPrize(user, month);

        double monthPrize = TempDB.mapMonthSaleMoney.get(user) * 0.03;

        System.out.println(user + " " + month + "月份業務獎金:" + (monthPrize) + "==>>");

        return monthPrize + money;
    }
}
/**
 * 裝飾器物件 ,計算累計獎金
 *
 * @author dy
 * @since 2016-08-11  & JDK 1.8.0_91
 */
public class SumPrizeDecorator extends Decorator {
    public SumPrizeDecorator(Component component) {
        super(component);
    }

    @Override
    public double calcPrize(String user, int month) {
        double money = super.calcPrize(user, month);

        double sumPrize = TempDB.mapMonthSaleMoney.get(user) * 0.001;
        System.out.println(user + " " + month + "月份累計獎金:" + (sumPrize) + "==>>");

        return sumPrize + money;
    }
}
/**
 * 裝飾器物件 ,計算累計獎金
 * @author dy
 * @since 2016-08-11  & JDK 1.8.0_91
 */
public class GroupPrizeDecorator extends Decorator {
    public GroupPrizeDecorator(Component component) {
        super(component);
    }

    @Override
    public double calcPrize(String user,int month) {

        double money = super.calcPrize(user,month);
        if (!isManager(user)){
            return money;
        }
        double totalPrize = 0.00;

        //團隊獎金 = 團隊總業務額 * 1%
        double prize = 0.00;
        for (double amount : TempDB.mapMonthSaleMoney.values()) {
            prize += amount;
        }
        prize = prize*0.01;
        System.out.println(user + " " +month+"月份團隊獎金:"+(prize)+"==>>");
        return  money + prize;
    }

    private boolean isManager(String user) {
        return TempDB.saleManager.get("經理").equals(user);
    }
}

以上將不同計算獎金方式拆解成不同元件,需要哪些獎金計算,自動裝置就可以了,讓我們來試試吧!

public class Client {

    public static void main(String[] args) {

        for (String user : TempDB.mapMonthSaleMoney.keySet()) {
            //建立基本獎金物件
            Component component = new ConcreteComponent();

            /**
             * 對基本獎金進行裝飾,這裡要組合各個裝飾
             */
            Decorator decorator1 = new MonthPrizeDecorator(component);
            Decorator decorator2 = new SumPrizeDecorator(decorator1);
            Decorator decorator3 = new GroupPrizeDecorator(decorator1);


            //這裡只需要用最後組合好的物件呼叫業務方法即可
            double prize = decorator3.calcPrize(user, 3);
            System.out.println(user + " " +3+"月份應得獎金:"+prize+"==>>");
        }
    }
}

執行結果,和我們原始程式碼結果一模一樣,但本質上卻更加靈活了

以上程式碼呼叫示例

轉載於:https://my.oschina.net/dyyweb/blog/731318