简单工厂模式与策略模式

《大话设计模式》第1、2章读书笔记,介绍简单工厂模式与策略模式

Posted by GrayWind on October 16, 2018

简单工厂模式

简单工厂模式想必大家都很熟悉了,它的目的是降低代码的耦合性,提高封装性与复用性。简单工厂模式可以很好的实现面向对象语言的多态性,可以想象现在有一个工厂,使用者告诉它我需要什么样的产品,工厂会根据要求新建出一个符合要求的对象。

如果要扩展新的实现类,只需要添加新的实现类并添加工厂的判断条件即可,不需要修改原有的实现。

简单计算器

以下面这个例子为例:现在要实现一个简单的两个运算数和一个操作符的计算器程序,支持加减乘除,要把运算操作封装为一个抽象类便于未来扩展新的操作符而不修改已有的实现,这时候就可以使用简单工厂类。从下面的UML图中可以看到,我们把运算类作为一个接口,加减乘除分别是它的一个实现类,用一个简单工厂类来决定哪个实现类被实例化。

SimpleFactory

接口代码如下,只有一个单一方法getResult获取计算结果

public interface Operation {
	public double getResult(double numberA, double numberB) throws Exception;
}

四个实现类

public class OperationAdd implements Operation {

	public double getResult(double numberA, double numberB) {
		return numberA + numberB;
	}

}

public class OperationSub implements Operation {

	public double getResult(double numberA, double numberB) {
		return numberA - numberB;
	}

}

public class OperationMul implements Operation {

	public double getResult(double numberA, double numberB) {
		return numberA * numberB;
	}

}

public class OperationDiv implements Operation {

	public double getResult(double numberA, double numberB) throws Exception {
		if(numberB == 0) {
			throw new Exception("除数不能为0");
		}
		return numberA / numberB;
	}

}

工厂类根据输入的操作符来判断要生成哪个实例对象

public class OperationFactory {
	public static Operation createOpeartion(String operate) {
		Operation operation = null;
		switch (operate) {
		case "+":
			operation = new OperationAdd();
			break;
		case "-":
			operation = new OperationSub();
			break;
		case "*":
			operation = new OperationMul();
			break;
		case "/":
			operation = new OperationDiv();
			break;
		default:
		}
		return operation;
	}
}

测试代码如下

public class Test {
	public static void main(String[] args) throws Exception {
		Scanner sc = new Scanner(System.in);
		System.out.println("请输入操作符");
		String operate = sc.next();
		Operation operation = OperationFactory.createOpeartion(operate);
		if(operation == null) {
			throw new Exception("输入的操作符不符合规范");
		}
		System.out.println("请第一个操作数");
		double numberA = sc.nextDouble();
		System.out.println("请第二个操作数");
		double numberB = sc.nextDouble();
		double result = operation.getResult(numberA, numberB);
		System.out.println("计算结果是: " + String.valueOf(result));
	}
}

商场促销

商场促销输入一个商品的价格和销售数量,要计算销售额。除了正常销售外,商场增加了打8折和满300返100两种销售策略。

尝试使用简单工厂来完成,CashSuper有三个实现类,分别对应正常销售、打折和满减

CashFactory

接口类如下,需要根据销售额返回计算值

public interface CashSuper {
	public double acceptCash(double money);
}

实现类分别是:直接返回,乘以折扣率,减去满减额

public class CashNormal implements CashSuper {
	@Override
	public double acceptCash(double money) {
		return money;
	}
}

public class CashRebate implements CashSuper {

	private double moneyRebate;

	public CashRebate(double rebate) {
		moneyRebate = rebate;
	}

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

public class CashReturn implements CashSuper {

	private double moneyCondition;
	private double moneyReturn;

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

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

最后工厂类,由于不同实现类需要的构造函数不一样所以需要分别处理

public class CashFactory {
	public static CashSuper createCashAccept(String type) {
		CashSuper cs = null;
		switch (type) {
		case "正常收费":
			cs = new CashNormal();
			break;
		case "打8折":
			cs = new CashRebate(0.8);
			break;
		case "满300减100":
			cs = new CashReturn(300, 100);
			break;
		default:
		}
		return cs;
	}
}

测试代码

public class FactroyTest {
	public static void main(String[] args) throws Exception {
		Scanner sc = new Scanner(System.in);
		System.out.println("请输入商品单价和数量");
		double price = sc.nextDouble();
		int number = sc.nextInt();
		System.out.println("请输入策略");
		String type = sc.next();
		CashSuper cs = CashFactory.createCashAccept(type);
		double result = cs.acceptCash(price * number);
		System.out.println("销售额:" + String.valueOf(result));
	}
}

策略模式与简单工厂模式结合

如果商场经常改动打折和满减策略,则需要修改工厂类,对于这类算法需要经常变动的场景要不影响客户的使用,可以采用策略模式。策略模式定义了算法家族,分别封装起来,让他们之间可以相互替换,此模式让算法的变化,不会影响到使用算法的客户。封装变化点是面向对象的一个重要思维方式。策略模式通过Context上下文,用一个ConcreteStrategy来配置,维护一个对Strategy对象的引用。

对于商场销售的问题来说,CashSuper就是抽象策略,而它的三个实现类是具体策略也就是策略模式中的具体算法。所以需要新增一个CashContext来封装,同时复用刚才的工厂方法来确定生成哪个实现类。

Strategy

只需要新增CashContext

public class CashContext {
	private CashSuper cs;

	public CashContext(String type) {
		this.cs = CashFactory.createCashAccept(type);
	}

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

测试代码

public class StrategyTest {
	public static void main(String[] args) throws Exception {
		Scanner sc = new Scanner(System.in);
		System.out.println("请输入商品单价和数量");
		double price = sc.nextDouble();
		int number = sc.nextInt();
		System.out.println("请输入策略");
		String type = sc.next();
		CashContext cc = new CashContext(type);
		double result = cc.getResult(price * number);
		System.out.println("销售额:" + String.valueOf(result));
	}
}

相比之下,策略模式结合简单工厂之后,测试代码只需要引用CashContext一个类,而工厂模式需要引用CashSuper和CashFactory两个类,如果后续出现策略变更只需要修改策略类CashContext而不需要修改使用者。

参考资料

《大话设计模式》第1、2章