策略模式以及与简单工厂模式的结合

(参考大话设计模式 程杰著)
在上一篇中,介绍了简单工厂模式,可以借今天的问题先回顾一下工厂模式。

问题:做一个商场收银软件,营业员根据客户购买的单价和数量,向客户收费。如图:在这里插入图片描述

你可能会想到,将前端界面写好,form表单提交给后端处理,返回给前端即可。的确,但是现在商场的营销策略变了,今天要打八折,明天要满300返100促销。由此,想让你用简单工厂来实现。

一、简单工厂实现

思路:不管是正常价格,打几折,还是返钱都可以看作是一种收钱方法。由此你可以想到可以把收钱方法抽象出来到父类,子类实现不同的收钱方法,再通过一个工厂,根据前端下拉框的内容来判断new什么子类对象,再通过多态调用某个具体的方法返回money,带到前端界面显示即可。下面是具体代码:

1.CashSuper类:

package com.shou.simplefactory.servlet;

//收费父类
abstract class CashSuper {
    public abstract double acceptCash(double money);
}

2.CashNormal类:

package com.shou.simplefactory.servlet;

//正常收费子类
public class CashNormal extends CashSuper{
    @Override
    public double acceptCash(double money) {
        return money;
    }
}

3.CashRebate类:

package com.shou.simplefactory.servlet;

//打折收费子类
public class CashRebate extends CashSuper{
    private double moneyRebate=1d;

    public CashRebate() {
    }

    public CashRebate(double moneyRebate) {
        this.moneyRebate = moneyRebate;
    }

    @Override
    public double acceptCash(double money) {
        return money*moneyRebate;
    }
}

4.CashReturn类:

package com.shou.simplefactory.servlet;

//返利收费子类
public class CashReturn extends CashSuper{
    private double moneyCondition=0.0d;
    private double moneyReturn=0.0d;

    public CashReturn() {
    }

    public CashReturn(double moneyCondition, double moneyReturn) {
        this.moneyCondition = moneyCondition;
        this.moneyReturn = moneyReturn;
    }

    @Override
    public double acceptCash(double money) {
        if (money>=moneyCondition){
            money=money-Math.floor(money/moneyCondition)*moneyReturn;
        }
        return money;
    }
}

5.工厂类CashFactory(关键):

package com.shou.simplefactory.servlet;

public class CashFactory {
    public static CashSuper createCashAccept(String str){
        CashSuper cashSuper=null;
        switch (str){
            case "正常收费":
                cashSuper=new CashNormal();
                break;
            case "打八折":
                cashSuper=new CashRebate(0.8);
                break;
            case "满300返100":
                cashSuper=new CashReturn(300,100);
                break;
        }
        return cashSuper;
    }
}

6.前端界面:

index2.jsp:

<%--
  Created by IntelliJ IDEA.
  User: Administrator
  Date: 2024/1/25
  Time: 0:46
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<form action="/tactics" method="post">
    <table>
        <thead>商场收银系统</thead>
        <tbody>
        <tr>
            <td>单价:</td>
            <th><input type="text" name="price"></th>
            <th><input type="submit" value="确定"></th>
        </tr>
        <tr>
            <td>数量:</td>
            <th><input type="text" name="count"></th>
            <th><input type="reset" value="重置"></th>
        </tr>
        <tr>
            <td>
                <label for="dropdown">选择结算方式:</label>
                <select id="dropdown" name="selectedValue">
                    <option value="正常收费">正常收费</option>
                    <option value="打八折">打八折</option>
                    <option value="满300返100">300100</option>
                </select>
            </td>
        </tr>
        <tr>
            <td colspan="3"><textarea>${list}</textarea></td>
        </tr>
        <tr>
            <td>总计: </td>
            <th>${total}</th>
        </tr>
        </tbody>

    </table>
</form>
</body>
</html>

7.处理前端界面的TacticsServlet:

package com.shou.simplefactory.servlet;

import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@WebServlet("/tactics")
public class TacticsServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=UTF-8");
        String priceString=req.getParameter("price");
        double price=Integer.parseInt(priceString);
        System.out.println(price);
        String countString=req.getParameter("count");
        double count=Integer.parseInt(countString);
        //此处获取到的是下拉选项的value而不是内容
        String selectedValue=req.getParameter("selectedValue");
        //测试
        System.out.println(selectedValue);
        //(1)先通过工厂new出来具体的收费策略对象
        CashSuper cashSuper=CashFactory.createCashAccept(selectedValue);
        //(2)调用子类对象的具体收费方法
        double totalPrice=0d;
        totalPrice=cashSuper.acceptCash(price*count);

        //(3)返回到前端多文本框
        List<String> list=new ArrayList<>();
        list.add("单价:"+price+"数量:"+count+"合计:"+totalPrice);
        req.setAttribute("list",list);

        //带到前端jsp界面,注意html是静态界面,不能用 RequestDispatcher dispatcher = request.getRequestDispatcher("/index2.jsp");
        req.setAttribute("total",totalPrice);
        RequestDispatcher dispatcher=req.getRequestDispatcher("/index2.jsp");
        dispatcher.forward(req,resp);

        System.out.println("总价为:"+totalPrice);
    }
}

由此功能基本完成:在这里插入图片描述

有了这个“收费对象生成工厂”,若再有其他的促销手段,即可在工厂类中增加一个分支,客户端和servlet改动一下即可。
但这里会有一个问题:简单工厂能解决这个问题,但这个模式只是解决对象的创建问题,而且由于工厂本身包括了所有的收费方式,商场可能经常更改打折额度和返利额度,每次维护和扩展收费方式都要改动这个工厂,以致代码重新编译部署,并不是最好的方法。由此引入今天的主角:策略模式

策略模式:它定义了算法家族,分别封装起来,让它们之间可以互相替换,次模式让算法的变化,不会影响到使用的客户。

商场收银的促销,都是一些算法。用工厂生成算法对象没错,但算法本身只是一种策略,最重要的是算法随时可能相互替换,这就是变化点。
下面是策略模式的结构图:请添加图片描述

二、策略模式实现

根据这个结构图,我们不难模仿写出这个CashContext类(Cash Super、CashNormal、CashRebate、CashReturn都不用改)。请添加图片描述
1.CashContext类:

package com.shou.tactics.servlet;

//替代工厂类

public class CashContext {
    private CashSuper cs;

    //通过 [构造方法] 传入具体的收费策略
    public CashContext(CashSuper csuper){
        this.cs=csuper;
    }
    public double GetResult(double money){
        return cs.acceptCash(money);
    }
}

2.servlet类:

package com.shou.tactics.servlet;

import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@WebServlet("/client")
public class Client extends HttpServlet {
    CashContext cashContext=null;

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=UTF-8");
        String priceString=req.getParameter("price");
        double price=Integer.parseInt(priceString);
        System.out.println(price);
        String countString=req.getParameter("count");
        double count=Integer.parseInt(countString);
        //此处获取到的是下拉选项的value而不是内容
        String selectedValue=req.getParameter("selectedValue");
        //测试
        System.out.println(selectedValue);

        switch (selectedValue){
            case "正常收费":
                cashContext=new CashContext(new CashNormal());
                break;
            case "打八折":
                cashContext=new CashContext(new CashRebate(0.8));
                break;
            case "满300返100":
                cashContext=new CashContext(new CashReturn(300,100));
                break;
        }
        double total=0d;
        total=cashContext.GetResult(count*price);

        List<String> list=new ArrayList<>();
        list.add("单价:"+price+"数量:"+count+"合计:"+total);
        req.setAttribute("list",list);
        req.setAttribute("total",total);

        RequestDispatcher dispatcher=req.getRequestDispatcher("/index2.jsp");
        dispatcher.forward(req,resp);

    }
}

这样明显没怎么改进,需要将判断的过程从servlet移走,可以想到策略模式与工厂模式结合,即在CashContext中进行过程判断。

三、策略与简单工厂结合

1.改进后的CashContext类:

package com.shou.improve.tactics.servlet;

//替代工厂类

public class CashContext {
    CashSuper cs=null;

    public CashContext(String type){
        switch (type){
            case "正常收费":
                CashNormal cs0=new CashNormal();
                cs=cs0;
                break;
            case "打八折":
                CashRebate cs1=new CashRebate(0.8);
                cs=cs1;
                break;
            case "满300返100":
                CashReturn cs2=new CashReturn(300,100);
                cs=cs2;
                break;
        }
    }

    public double GetResult(double money){
        return cs.acceptCash(money);
    }
}

2.servlet类:

package com.shou.improve.tactics.servlet;

import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@WebServlet("/improveclient")
public class Client extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=UTF-8");
        String priceString=req.getParameter("price");
        double price=Integer.parseInt(priceString);
        System.out.println(price);
        String countString=req.getParameter("count");
        double count=Integer.parseInt(countString);
        //此处获取到的是下拉选项的value而不是内容
        String selectedValue=req.getParameter("selectedValue");
        //测试
        System.out.println(selectedValue);

        //!这里只需要让客户端认识这一个CashContext类即可,
        // 不需要像简单工厂一样,将字符串先给工厂,返回具体的子类对象(CashSuper cashSuper=CashFactory.createCashAccept(selectedValue);)
        CashContext cashContext1=new CashContext(selectedValue);

        double total=0d;
        total=cashContext1.GetResult(count*price);


        List<String> list=new ArrayList<>();
        list.add("单价:"+price+"数量:"+count+"合计:"+total);
        req.setAttribute("list",list);
        req.setAttribute("total",total);

        RequestDispatcher dispatcher=req.getRequestDispatcher("/index2.jsp");
        dispatcher.forward(req,resp);

    }
}

至此,我们可以只让客户端认识CashContext类即可,不用像简单工厂一样要认识CashSuper和CashFactory类,降低了耦合度。
对比不同:
请添加图片描述

当然这个模式解决这个问题并不是最完美的,因为在CashContext中依然有switch来判断,增加条件进行更改的麻烦,后面抽象工厂模式进行讲解。
关注我不迷路~

  • 24
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值