简单工厂模式
题目:
请用 C+、Java、C#或 VBNET 任意一种 面向对象 语言实现一个计算器控制台程序,要求输入两个数和运算符号,得到结果
版本一
public static void main(String[] args) {
System.out.println("请输入数字A: ");
Scanner sc = new Scanner(System.in);
// 命名不规范
int A = sc.nextInt();
System.out.println("请输入运算符号: ");
String B = sc.nextLine();
System.out.println("请输入数字B: ");
int C = sc.nextInt();
Integer D = null;
// 判断分支 意味每个条件都要判断 等于计算机做了三次无用工
if (B.equals("+")) D = A + C;
if (B.equals("-")) D = A - C;
if (B.equals("*")) D = A * C;
// 客户输入0怎么办如果输入的是字符号而不是数
if (B.equals("/")) D = A / C;
System.out.println("结果: " + D);
}
上面虽然实现了自己想要的结果,代码也没有问题但是就有很多初学者的毛病,比如命名不规范,判断分支 意味每个条件都要判断,等于计算机做了三次无用工,进行除法运算的时候,客户输入0怎么办如果输入的是字符号而不是数,也就是说没有进行异常相关的处理
版本二
public static void main(String[] args) {
System.out.println("请输入数字A: ");
Scanner sc = new Scanner(System.in);
int strNumberA = sc.nextInt();
System.out.println("请输入运算符号: ");
String strNumberB = sc.nextLine();
System.out.println("请输入数字B: ");
int strNumberC = sc.nextInt();
Integer strNumberD = null;
try {
switch (strNumberB) {
case "+":
strNumberD = strNumberA + strNumberC;
break;
case "-":
strNumberD = strNumberA - strNumberC;
break;
case "*":
strNumberD = strNumberA * strNumberC;
break;
case "/":
if ((strNumberC != 0)) strNumberD = strNumberA / strNumberC;
break;
default:
System.out.println("输入的运算符号有误");
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("输入的运算符号有误");
}
// 判断分支 意味每个条件都要判断 等于计算机做了三次无用工
System.out.println("结果: " + strNumberD);
}
上面的代码实现计算器是没有问题,但是题目是让我们用面向对象的方式去实现。假设上面实现的是一个mac风格的计算器,那么我现在还要一个windos的计算器,那么我们需要将重复逻辑复制到其他代码中,也就是说 CTRL + C 和 CTRL + V 但是当代码多到一定程度的时候维护就会很难,越大的系统就会越严重。所以我们应该尽可能的避免服用,即计算器只用于计算,显示器只用于显示
版本三
Main.class
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
double D;
try {
System.out.println("请输入数字A");
double A = sc.nextInt();
System.out.println("请输入运算符号: ");
String B = sc.nextLine();
System.out.println("请输入数字C: ");
double C = sc.nextInt();
D = Operation.GetResult(A, C, B);
} catch (Exception e) {
throw new RuntimeException(e);
}
System.out.println("结果: " + D);
}
public class Operation {
// 将运算和显示界面分开
public static double GetResult(double numberA, double numberB, String operate) {
double result = 0;
switch (operate) {
case "+":
result = numberA + numberB;
break;
case "-":
result = numberA - numberB;
break;
case "*":
result = numberA * numberB;
break;
case "/":
if (numberB != 0) result = numberA / numberB;
break;
default:
System.out.println("输入的运算符号有误");
}
return result;
}
}
在第三个版本中我们将显示页面和业务进行了分离,这样简单的说我们运用了封装的一个手法,但是面向对象相关的继承和多态我们并没有进行使用
版本四
当前我们已经把界面和业务进行了分离,但是现在如果我们要再加一个开根(sqrt)运算的话,我们需要改动Operation,在switch添加一个分支。但是当前我们我只需要进行开根,却让加减程序同时参与编译,即本来是只增加一个功能但是也会让其他运行良好的代码重新编译产生了影响,所以我们应该将加减乘除进行分离,修改其中一个不应该影响其他的。
Main.class
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
double D;
try {
System.out.println("请输入数字A");
double A = sc.nextInt();
System.out.println("请输入运算符号: ");
String B = sc.nextLine();
System.out.println("请输入数字C: ");
double C = sc.nextInt();
D = Operation.GetResult(A, C, B);
} catch (Exception e) {
throw new RuntimeException(e);
}
System.out.println("结果: " + D);
}
public class Operation {
// 将运算和显示界面分开
public static double GetResult(double numberA, double numberB, String operate) {
double result = 0;
switch (operate) {
case "+":
result = numberA + numberB;
break;
case "-":
result = numberA - numberB;
break;
case "*":
result = numberA * numberB;
break;
case "/":
if (numberB != 0) result = numberA / numberB;
break;
default:
System.out.println("输入的运算符号有误");
}
return result;
}
}
Main.class
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
double D;
try {
System.out.println("请输入数字A");
double A = sc.nextInt();
System.out.println("请输入运算符号: ");
String B = sc.nextLine();
System.out.println("请输入数字C: ");
double C = sc.nextInt();
D = Operation.GetResult(A, C, B);
} catch (Exception e) {
throw new RuntimeException(e);
}
System.out.println("结果: " + D);
}
public class Operation {
// 将运算和显示界面分开
public static double GetResult(double numberA, double numberB, String operate) {
double result = 0;
switch (operate) {
case "+":
result = numberA + numberB;
break;
case "-":
result = numberA - numberB;
break;
case "*":
result = numberA * numberB;
break;
case "/":
if (numberB != 0) result = numberA / numberB;
break;
default:
System.out.println("输入的运算符号有误");
}
return result;
}
}
Main.class
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
double D;
try {
System.out.println("请输入数字A");
double A = sc.nextInt();
System.out.println("请输入运算符号: ");
String B = sc.nextLine();
System.out.println("请输入数字C: ");
double C = sc.nextInt();
D = Operation.GetResult(A, C, B);
} catch (Exception e) {
throw new RuntimeException(e);
}
System.out.println("结果: " + D);
}
public class Operation {
// 将运算和显示界面分开
public static double GetResult(double numberA, double numberB, String operate) {
double result = 0;
switch (operate) {
case "+":
result = numberA + numberB;
break;
case "-":
result = numberA - numberB;
break;
case "*":
result = numberA * numberB;
break;
case "/":
if (numberB != 0) result = numberA / numberB;
break;
default:
System.out.println("输入的运算符号有误");
}
return result;
}
}
Main.class
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
double D;
try {
System.out.println("请输入数字A");
double A = sc.nextInt();
System.out.println("请输入运算符号: ");
String B = sc.nextLine();
System.out.println("请输入数字C: ");
double C = sc.nextInt();
D = Operation.GetResult(A, C, B);
} catch (Exception e) {
throw new RuntimeException(e);
}
System.out.println("结果: " + D);
}
public class Operation {
// 将运算和显示界面分开
public static double GetResult(double numberA, double numberB, String operate) {
double result = 0;
switch (operate) {
case "+":
result = numberA + numberB;
break;
case "-":
result = numberA - numberB;
break;
case "*":
result = numberA * numberB;
break;
case "/":
if (numberB != 0) result = numberA / numberB;
break;
default:
System.out.println("输入的运算符号有误");
}
return result;
}
}
OperationSub.class
public class OperationSub extends Operation {
@Override
public double GetResult() {
double result = 0;
result = get_numberA() - get_numberB();
return result;
}
}
在上面的代码中我们需要子类重写GetResult方法,这样如果我们要修改任何一个算法就不需要提供其他的算法代码了,只需要改动自己想要改动的就是了,但是出现了一个新的问题就是如果让计算机知道我希望使用的哪一个算法呢,即简单工厂模式
OperationFactory.calss
public class OperationFactory {
public static Operation createOperate(String operation) {
Operation oper = null;
switch (operation) {
case "+":
oper = new OperationAdd();
break;
case "-":
oper = new OperationSub();
break;
case "*":
oper = new OperationMul();
break;
case "/":
oper = new OperationDiv();
break;
}
return oper;
}
}
Main.calss
public class Main {
public static void main(String[] args) {
Operation operate = OperationFactory.createOperate("+");
operate.set_numberA(1);
operate.set_numberB(2);
double result = operate.GetResult();
System.out.println("result = " + result);
}
}
即这样我们只需要输入运算符号,工厂就能实例化出合适的对象,通过多态返回父类的方式实现了计算器的结果,UML类图如下
UML类图
类如下
public class 企鹅 extends 鸟 {
@Override
public void 下蛋() {
}
private 气候 气候;
}
public abstract class 动物 {
public String 有生命;
/**
* 新陈代谢
*
* @param 氧气 氧气
* @param 水 水
*/
public abstract void 新陈代谢(氧气 氧气, 水 水);
/**
* 繁殖
*/
public abstract void 繁殖();
}
public class 唐老鸭 extends 鸭子 implements 讲话 {
@Override
public void 讲话() {
}
}
public class 大雁 extends 鸟 implements 飞 {
@Override
public void 下蛋() {
}
@Override
public void 飞() {
}
}
public class 气候 {
}
public class 氧气 {
}
public class 水 {
}
public class 翅膀 {
}
public interface 讲话 {
void 讲话();
}
public class 雁群 {
private 大雁[] array大雁;
public void V形飞行() {
}
public void 一形飞行() {
}
}
public interface 飞 {
void 飞();
}
public abstract class 鸟 extends 动物 {
public String 羽毛;
public String 有角质喙没有牙齿;
private 翅膀 翅膀;
@Override
public void 新陈代谢(氧气 氧气, 水 水) {
}
@Override
public void 繁殖() {
}
public 鸟() {
翅膀 = new 翅膀();
}
public abstract void 下蛋();
}
public class 鸭子 extends 鸟 {
@Override
public void 下蛋() {
}
}
策略模式
题目:做一个商场收银软件,营业员根据客户所购买商品的单价和数量,向客户收费。
版本一
public class Porgram_1 {
// 计算总计
double total = 0.0d;
// 单价
double price;
// 数量
Integer Num;
private void btnOk_Click() {
price = 2;
Num = 3;
double totalPrices = price * Num;
total = total + totalPrices;
System.out.println("单价" + price + "每个" + Num + "个" + "总计:" + total);
}
}
上面确实能达到题目的一个效果,但是如果我要添加新的计费方式就需要更改源代码即可扩展性和灵活性太低
如果我想要在商场对商品搞活动比如 对商品打8折就需要添加一个新的变量(折扣) 计算的时候*折扣 直接更改源代码
增加新需求: 对商品打8折
版本二
public class Porgram_2 {
// 计算总计
double total = 0.0d;
// 单价
double price;
// 数量
Integer Num;
private void btnOk_Click() {
price = 2;
Num = 3;
打折 打 = 打折.打5折;
//根据打折选择不同的单价 4个分支除了打折多少之外没有什么不同需要考虑重构
switch (打) {
case 正常收费:
price = price;
break;
case 打8折:
price = price * 0.8;
break;
case 打7折:
price = price * 0.7;
break;
case 打5折:
price = price * 0.5;
break;
}
double totalPrices = price * Num;
total = total + totalPrices;
System.out.println("单价" + price + "每个" + Num + "个" + "总计:" + total);
}
}
enum 打折 {
正常收费("正常收费"), 打8折("打8折"), 打7折("打7折"), 打5折("打5折"),;
String 折扣;
打折(String 折扣) {
this.折扣 = 折扣;
}
public String get折扣() {
return 折扣;
}
public void set折扣(String 折扣) {
this.折扣 = 折扣;
}
}
上面代码比第一版就要灵活多了,但是四个分支要执行的语句除了打折多少以外几乎没有什么不同,所以应该考虑重构一下。
增加新需求:商场活动增大,需要有满300返100的促销算法
版本三
如果我们使用简单工厂的话就可以很方便的实现,毕竟打折基本都是一样的
public class CashFactory {
public static CashSuper createCashAccept(Price price) {
CashSuper cashSuper = null;
switch (price) {
case 打折 -> cashSuper = new CashRebate(0.8);
case 满300返100 -> cashSuper = new CashReturn(300, 100);
case 正常收费 -> cashSuper = new CashNormal();
}
return cashSuper;
}
}
public class CashNormal extends CashSuper{
@Override
public double accepCash(double money) {
return money;
}
}
public class CashRebate extends CashSuper {
private double moneyRebate = 1d;
public CashRebate(double moneyRebate) {
this.moneyRebate = moneyRebate;
}
@Override
public double accepCash(double money) {
return money * moneyRebate;
}
}
public class CashReturn extends CashSuper {
private double moneyCondition = 0.0d;
private double moneyReturn = 0.0d;
/**
* 返回收费,初始化时候必须输入返利条件和返利值
*
* @param moneyCondition
* @param moneyReturn
*/
public CashReturn(double moneyCondition, double moneyReturn) {
this.moneyCondition = moneyCondition;
this.moneyReturn = moneyReturn;
}
@Override
public double accepCash(double money) {
double result = money;
// 如果大于返利条件,则需要减去返利值
if (money > moneyCondition) result = money - Math.floor(money / moneyCondition) * moneyReturn;
return result;
}
}
public abstract class CashSuper {
/**
* 现金收取超类的抽象方法,收取现金,参数为原价,返回为当前价格
*
* @param money
* @return
*/
public abstract double accepCash(double money);
}
public class Main {
public static void main(String[] args) {
CashSuper cashAccept = CashFactory.createCashAccept(Price.正常收费);
double total = 0.0d;
double totalPrices = 0d;
double nums = 3d;
totalPrices = cashAccept.accepCash(10);
total = total + totalPrices * nums;
System.out.println("单价 " + 10 + "数量 " + nums + "实际价格" + totalPrices + " 总价 " + total);
}
}
经过上面的改动,如果当我们再新增新需求的时候,比如满100积分 10点,以后积分到一定地步了可以领取奖品
-
新增积分算法,构造方法有2参数条件和返点,
-
继承CashSuper
-
更改枚举类,再到工厂里进行修改
虽然工厂模式解决了创建对象的问题,但是因为工厂包括了所有的收费方式,商场可能经常性的更改打折额度和返利力度,每次维护都要改动这个工厂以至于代码需要重新编译。
版本四
策略模式定义及案例:
策略模式定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户
public class ConcreteStrategyA extends Strategy{
@Override
public void AlgorithmInterface() {
System.out.println("算法A实现");
}
}
public class ConcreteStrategyB extends Strategy{
@Override
public void AlgorithmInterface() {
System.out.println("算法B实现");
}
}
public class ConcreteStrategyC extends Strategy{
@Override
public void AlgorithmInterface() {
System.out.println("算法C实现");
}
}
public class ConcreteStrategyC extends Strategy{
@Override
public void AlgorithmInterface() {
System.out.println("算法C实现");
}
}
public class ConcreteStrategyC extends Strategy{
@Override
public void AlgorithmInterface() {
System.out.println("算法C实现");
}
}
public class Main {
public static void main(String[] args) {
Context context;
context = new Context(new ConcreteStrategyA());
context.ContextInterface();
context = new Context(new ConcreteStrategyB());
context.ContextInterface();
context = new Context(new ConcreteStrategyC());
context.ContextInterface();
}
}
商场改策略
所以说版本三写的CashSuper就是抽象策略,而正常收费,打折收费和返利收费 就是三个不同的策略
public class CashContext {
private CashSuper cashSuper;
public CashContext(CashSuper cashSuper) {
this.cashSuper = cashSuper;
}
public double GetResult(double money) {
return cashSuper.accepCash(money);
}
}
public class Main {
public static void main(String[] args) {
CashContext context;
Price 打 = Price.正常收费;
// 根据打折选择不同的单价 4个分支除了打折多少之外没有什么不同需要考虑重构
switch (打) {
case 正常收费:
context = new CashContext(new CashNormal());
break;
case 满300返100:
context = new CashContext(new CashReturn(30, 100));
break;
case 打折:
context = new CashContext(new CashRebate(0.8));
break;
}
}
}
public enum Price {
正常收费("正常收费"), 满300返100("满300返100"), 打折("打折"),
;
String 折扣;
Price(String 折扣) {
this.折扣 = 折扣;
}
public String get折扣() {
return 折扣;
}
public void set折扣(String 折扣) {
this.折扣 = 折扣;
}
}
版本五
虽然这样也实现了自己想要的但是如果这样写还不是在客户端-显示端(Main)判断用那一个算法,之前我们是用简单工厂模式进行转移的,现在依旧可以和测试类的Context结合一下
public class CashContext {
CashSuper cashSuper = null;
/**
*
* @param price 传入的是收费的类型
*/
public CashContext(Price price) {
switch (price) {
case 正常收费:
cashSuper = new CashNormal();
break;
case 满300返100:
cashSuper = new CashReturn(300, 100);
break;
case 打折:
cashSuper = new CashRebate(0.8);
break;
}
}
public double GetResult(double money) {
return cashSuper.accepCash(money);
}
}
public class Main {
public static void main(String[] args) {
// 单价
double price = 0;
// 数量
Integer Num = null;
CashContext context = new CashContext(Price.打折);
double totalPrices = 0d;
totalPrices = context.GetResult(price * Num);
System.out.println("单价" + price + "每个" + Num + "个" + "总计:" + totalPrices);
}
}
这样就又把业务和显示进行分离了
如果和之前简单工厂模式和现在的策略工厂模式进行对比会发现,简单工厂我需要让客户认识两个类CashSuper和CashFactory,而策略工厂模式的话客户端就只需要知道CashContext就可以了耦合度更低。使得具体的收费业务和显示彻底分离,就连父类的CashSuper都不需要让客户端认识了
策略模式解析
-
策略模式是一种定义一系列算法的东西,从概念上看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合
-
策略模式的Strategy类层次为Context定义了一系列的可供重用的算法或行为。继承有助于析取出这些算法的公共功能
-
策略模式简化了单元测试类,因为每个算法都有自己的类,可以通过自己的接口单独测试
-
当不同的行为行为堆砌在一个类中的时候,就很难避免使用条件语句来选择合适的行为。将这些行为封装在一个个独立的Strategy类中,可以在使用这些行为的类中消除条件语句
-
策略模式就是用来封装算法的,但在实践中,我们发现可以用它来封装几乎任何类型的规则,只要在分析过程中听到需要在不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性
-
在基本的策略模式中,选择所用具体实现的职责由客户端对象承担,并且转给策略模式的Context对象,这本身没有接触客户端需要选择判断的压力但是结合之后选择具体的实现也由Context承担,最大化地减轻了客户端的职责
缺点:
-
在CashContext中还是用到了switch,也就是说如果我们需要新增一个算法还需要改动switch就还是让人不爽,因为任何变动都是有成本的
-
所以我们可以用到反射来解决