如果物件本身有负责计算的方法,且该方法依照给予的参数,会有不同的计算结果,那可以将计算的部分封装成独立的物件,彼此可以互相切换,同时不影响原有的功能。
试想一个情境,物件本身负责计算的功能,像是计算总金额、计算最佳路线、计算事件发生的机率等等,如果给予不同的参数,则计算的结果会大相径庭。一段时间过後,方法可能会增加许多,多少增加管理上的麻烦。
为了方便管理,可以将计算的部分封装成独立的物件,而且物件本身都采用相同的规格,对外有一致的方法。如此一来,物件需要计算时,只要根据参数就能选定相关的计算物件,而且,采用相同的方法,呼叫任意计算物件都可以用相同的方法,简化物件之间沟通上的麻烦。
在书上,会称呼计算部分为演算法(Algorithm)。
作法是:
Context
)Strategy
)Context
本身会储存目前使用的 Strategy
,有计算需求时则使用 Strategy
的统一方法。Context
储存的 Strategy
。以下范例以「简易动物园门票收费机」为核心制作。
制作入园者物件:Person
public class Person {
private String name;
private boolean hasStudentID;
public Person(String name, boolean hasStudentID) {
this.name = name;
this.hasStudentID = hasStudentID;
}
public String getName() {
return name;
}
public boolean isHasStudentID() {
return hasStudentID;
}
}
制作计算物件的虚拟层亲代:Strategy
public interface Strategy {
int calculateFees(List<Person> people);
}
制作计算物件的子代:StandardStrategy
、GroupDiscountStrategy
(Strategy 物件)
public class StandardStrategy implements Strategy {
@Override
public int calculateFees(List<Person> people) {
int totalFees = 0;
for (Person person : people) {
if (person.isHasStudentID()) {
totalFees += 30;
} else {
totalFees += 60;
}
}
return totalFees;
}
}
public class GroupDiscountStrategy implements Strategy {
@Override
public int calculateFees(List<Person> people) {
int totalFees = 0;
for (Person person : people) {
if (person.isHasStudentID()) {
totalFees += 30;
} else {
totalFees += 60;
}
}
return (int) (totalFees * 0.7);
}
}
制作动物园门票售票机:ZoomTicketVendingMachine
(Context 物件)
public class ZoomTicketVendingMachine {
private Strategy strategy;
private List<Person> people;
public ZoomTicketVendingMachine() {
people = new ArrayList<>();
}
public void setStrategy(int peopleCounts) {
if (peopleCounts >= 30) {
strategy = new GroupDiscountStrategy();
} else {
strategy = new StandardStrategy();
}
}
public void addPerson(Person person) {
people.add(person);
}
public void removePerson(Person person) {
people.remove(person);
}
public int calculateFees() {
setStrategy(people.size());
return strategy.calculateFees(people);
}
public void clear() {
people.clear();
}
}
测试,模拟一家四口以及校外校学买动物园门票:TicketMachineStrategySample
public class TicketMachineStrategySample {
public static void main(String[] args) throws Exception {
ZoomTicketVendingMachine ticketMachine = new ZoomTicketVendingMachine();
System.out.println("---一家四口,两大两小---");
ticketMachine.addPerson(new Person(NameSelector.exec("m"), false));
ticketMachine.addPerson(new Person(NameSelector.exec("f"), false));
ticketMachine.addPerson(new Person(NameSelector.exec("m"), true));
ticketMachine.addPerson(new Person(NameSelector.exec("f"), true));
int familyFees = ticketMachine.calculateFees();
System.out.println("家庭的总金额是: " + familyFees);
ticketMachine.clear();
System.out.println("---户外教学,两个导师以及三十八个学生---");
ticketMachine.addPerson(new Person(NameSelector.exec("m"), false));
ticketMachine.addPerson(new Person(NameSelector.exec("f"), false));
for (int i = 0; i < 19; i++) {
ticketMachine.addPerson(new Person(NameSelector.exec("f"), true));
}
for (int i = 0; i < 19; i++) {
ticketMachine.addPerson(new Person(NameSelector.exec("m"), true));
}
int schoolTripFees = ticketMachine.calculateFees();
System.out.println("校外教学的总金额是: " + schoolTripFees);
}
}
Utils:NameSelector
public class NameSelector {
private static Random random = new Random();
public static String exec(String gender) throws IOException, ParseException, Exception {
// 读取 JSON Array,内容是字串阵列
JSONParser parser = new JSONParser();
Object obj = null;
if (gender.equals("m")) {
obj = parser.parse(new FileReader("./src/utils/boyNameList.json"));
} else if (gender.equals("f")) {
obj = parser.parse(new FileReader("./src/utils/girlNameList.json"));
} else {
throw new Exception("性别代号错误!\n输入的性别参数是: " + gender);
}
JSONArray jsonArray = (JSONArray) obj;
// 0 - 149
int option = random.nextInt(149 + 0) + 0;
return (String) jsonArray.get(option);
}
}
制作入园者物件:Person
class Person {
/**
* @param {string} name
* @param {boolean} hasStudentID
*/
constructor(name, hasStudentID) {
this.name = name;
this.hasStudentID = hasStudentID;
}
getName() {
return this.name;
}
isHasStudentID() {
return this.hasStudentID;
}
}
制作计算物件的虚拟层亲代:Strategy
/**
* @abstract
*/
class Strategy {
/**
* @abstract
* @param {Person[]} people
*/
calculateFees(people) { return 0; }
}
制作计算物件的子代:StandardStrategy
、GroupDiscountStrategy
(Strategy 物件)
class StandardStrategy extends Strategy {
/**
* @override
* @param {Person[]} people
*/
calculateFees(people) {
let totalFees = 0;
for (const person of people) {
if (person.isHasStudentID()) {
totalFees += 30;
} else {
totalFees += 60;
}
}
return totalFees;
}
}
class GroupDiscountStrategy extends Strategy {
/**
* @override
* @param {Person[]} people
*/
calculateFees(people) {
let totalFees = 0;
for (const person of people) {
if (person.isHasStudentID()) {
totalFees += 30;
} else {
totalFees += 60;
}
}
return totalFees * 0.7;
}
}
制作动物园门票售票机:ZoomTicketVendingMachine
(Context 物件)
class ZoomTicketVendingMachine {
constructor() {
/** @type {Strategy} */
this.strategy = null;
/** @type {Person[]} */
this.people = [];
}
/** @param {number} peopleCounts */
setStrategy(peopleCounts) {
if (peopleCounts >= 30) {
this.strategy = new GroupDiscountStrategy();
} else {
this.strategy = new StandardStrategy();
}
}
/** @param {Person} person */
addPerson(person) {
this.people.push(person);
}
/** @param {Person} person */
removePerson(person) {
this.people = this.people.filter(item => item !== person);
}
calculateFees() {
this.setStrategy(this.people.length);
return this.strategy.calculateFees(this.people);
}
clear() {
this.people = [];
}
}
测试,模拟一家四口以及校外校学买动物园门票:ticketMachineStrategySample
const ticketMachineStrategySample = () => {
const ticketMachine = new ZoomTicketVendingMachine();
console.log("---一家四口,两大两小---");
ticketMachine.addPerson(new Person(nameSelector("m"), false));
ticketMachine.addPerson(new Person(nameSelector("f"), false));
ticketMachine.addPerson(new Person(nameSelector("m"), true));
ticketMachine.addPerson(new Person(nameSelector("f"), true));
const familyFees = ticketMachine.calculateFees();
console.log("家庭的总金额是: " + familyFees);
ticketMachine.clear();
console.log("---户外教学,两个导师以及三十八个学生---");
ticketMachine.addPerson(new Person(nameSelector("m"), false));
ticketMachine.addPerson(new Person(nameSelector("f"), false));
for (let i = 0; i < 19; i++) {
ticketMachine.addPerson(new Person(nameSelector("f"), true));
}
for (let i = 0; i < 19; i++) {
ticketMachine.addPerson(new Person(nameSelector("m"), true));
}
const schoolTripFees = ticketMachine.calculateFees();
console.log("校外教学的总金额是: " + schoolTripFees);
}
ticketMachineStrategySample();
Utils:nameSelector
/** @param {string} gender */
const nameSelector = (gender) => {
const option = Math.floor(Math.random() * (149 - 0 + 1)) + 0;
// 读取 JSON Array,内容是字串阵列
if (gender === "m") {
const boyNameList = require('../Sample-by-Java/src/utils/boyNameList.json');
return boyNameList[option];
} else if (gender === "f") {
const girlNameList = require('../Sample-by-Java/src/utils/girlNameList.json');
return girlNameList[option];
} else {
throw new Error(`性别参数错误,输入的参数是: ${gender}`);
}
}
Strategy 模式跟 Simple Factory Method 十分类似,皆拥有 if - else if - else
或 switch case
而有多个可能的选择,两者最大的不同在於前者专注在将 Business Logic 抽出;後者专注在如何「产出」需要的物件。当然,两者的类别不同,理所当然在乎不同的点。
实作上要注意的,与 State 模式相似,什麽样的情境下,需要将 if - else if - else
或 switch case
抽出、封装成独立物件?就我工作经验来说,如果之後在开发上会建立许多负责计算的物件,那可以提早封装,节省之後要套用 Strategy 模式的时间。反之,如果不会建立许多负责计算的物件,那不用套用 Strategy 模式,维持 if - else if - else
或 switch case
也很好。
明天将介绍 Behavioural patterns 的第十个模式:Template Method 模式。
>>: Day26 - 云端交易主机 - GCP云端平台申请&架设(Ubuntu)
今天的实作内容主要根据教学网站进行。 将应用程序安装到Heroku (接续Day27) 使用GIT将...
前情提要 上回与艾草玩游戏输了要接受处罚。 「都躲这麽远了,她应该找不到我了吧!」 艾草:「啊哈,原...
ISO 27001 机房管理部份之三 稽核分三种 : 内部稽核 (例如 : 稽核组长、稽核小组) 外...
什麽是真值与假值 在 JavaScript 中,除了布林值本身就是真值或假值外,其他型别会在布林的执...
https://leetcode.com/problems/3sum-closest/ 3Sum ...