(参考大话设计模式 程杰著)
在上一篇中,介绍了简单工厂模式,可以借今天的问题先回顾一下工厂模式。
问题:做一个商场收银软件,营业员根据客户购买的单价和数量,向客户收费。如图:![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/df0b7d677e9745a483a5c2f55bfac440.png)
你可能会想到,将前端界面写好,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">满300返100</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);
}
}
由此功能基本完成:![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/4fe3fb9c7ec6475887544e9fc4f4fa90.png)
有了这个“收费对象生成工厂”,若再有其他的促销手段,即可在工厂类中增加一个分支,客户端和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来判断,增加条件进行更改的麻烦,后面抽象工厂模式进行讲解。
关注我不迷路~