策略模式 VS 橋樑模式
- 策略模式:是使用繼承和多態建立一套可以自由切換算法的模式;只有一個抽象角色,可以沒有實現,也可以有很多實現。
- 橋樑模式:是在不破壞封裝的前提下解決抽象和實現都可以獨立擴展的模式。有兩個“橋墩”——抽象化角色和實現化角色,只要橋墩搭建好,橋就有了。
策略模式和橋樑模式兩者的通用類圖非常相似,只相差了圖中紅框區域,其中左圖爲策略模式,右圖爲橋樑模式。
(1)策略模式
郵件一般有兩種格式:文本郵件(Text Mail)和超文本郵件(HTML MaiL)。使用策略模式發送郵件,則認爲這兩種郵件是兩種不同的封裝格式,給定了發件人、收件人、標題、內容的一封郵件,按照兩種不同的格式分別進行封裝,然後發送。按照這樣的分析,我們發現郵件的兩種不同封裝格式就是兩種不同的算法,具體到策略模式就是兩種不同策略。
public abstract class MailTemplate {
private String from; //發件人
private String to; //收件人
private String subject; //標題
private String context; //內容
public MailTemplate(String from, String to, String subject, String context) {
this.from = from;
this.to = to;
this.subject = subject;
this.context = context;
}
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getContext() {
return context;
}
public void setContext(String context) {
this.context = context;
}
}
這裏定義了一個沒有抽象的方法的抽象類MailTemplate,其意義爲:包含郵件的所有屬性,但不能被直接實例化爲對象,需要通過兩種郵件格式進行郵件的實例化。
import com.sfq.impl.MailTemplate;
public class HtmlMail extends MailTemplate {
public HtmlMail(String from, String to, String subject, String context) {
super(from, to, subject, context);
}
public String getContext() {
String context = "\nContext-Type:multipart/mixed;charset=GB2312\n" + super.getContext();
context = context + "\n郵件格式爲:超文本格式";
return context;
}
}
import com.sfq.impl.MailTemplate;
public class TextMail extends MailTemplate {
public TextMail(String from, String to, String subject, String context) {
super(from, to, subject, context);
}
public String getContext() {
String context = "\nContext-Type:text/plain;charset=GB2312\n" + super.getContext();
context = context + "\n郵件格式爲:文本格式";
return context;
}
}
import com.sfq.impl.MailTemplate;
public class MailServer {
private MailTemplate mailTemplate;
public MailServer(MailTemplate mailTemplate) {
this.mailTemplate = mailTemplate;
}
public void sendMail() {
System.out.println("------發送郵件-----");
System.out.println("發件人:" + mailTemplate.getFrom());
System.out.println("收件人:" + mailTemplate.getTo());
System.out.println("郵件標題:" + mailTemplate.getSubject());
System.out.println("郵件內容:" + mailTemplate.getContext());
}
}
import com.sfq.action.HtmlMail;
import com.sfq.action.MailServer;
import com.sfq.impl.MailTemplate;
public class Client {
public static void main(String[] args) {
MailTemplate textMail = new HtmlMail("[email protected]", "[email protected]", "特大驚喜", "本店清倉,全場兩折!");
MailServer mailServer = new MailServer(textMail);
mailServer.sendMail();
}
}
結果
------發送郵件-----
發件人:[email protected]
收件人:[email protected]
郵件標題:特大驚喜
郵件內容:
Context-Type:multipart/mixed;charset=GB2312
本店清倉,全場兩折!
郵件格式爲:超文本格式
在該場景中,使用策略模式實現兩種算法的自由切換,它提供了這樣的保證:封裝郵件的兩種行爲是可選擇的,至於選擇哪個算法是由上層模塊決定的。策略模式要完成的任務就是提供兩種可以替換的算法。
(2)橋樑模式
橋樑模式關注的是抽象和實現的分離,它是結構型模式,結構型模式研究的是如何建立一個軟件架構。
MailTemplate抽象類中增加了一個添加發送信息的函數:
public abstract class MailTemplate {
private String from; //發件人
private String to; //收件人
private String subject; //標題
private String context; //內容
public MailTemplate(String from, String to, String subject, String context) {
this.from = from;
this.to = to;
this.subject = subject;
this.context = context;
}
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getContext() {
return context;
}
public void setContext(String context) {
this.context = context;
}
//允許增加郵件發送標誌
public void add(String sendInfo) {
context = sendInfo + context;
}
}
兩個郵件的實現類不變,依然是重寫獲取郵件內容的方法getContext():
import com.sfq.impl.MailTemplate;
public class HtmlMail extends MailTemplate {
public HtmlMail(String from, String to, String subject, String context) {
super(from, to, subject, context);
}
public String getContext() {
String context = "\nContext-Type:multipart/mixed;charset=GB2312\n" + super.getContext();
context = context + "\n郵件格式爲:超文本格式";
return context;
}
}
import com.sfq.impl.MailTemplate;
public class TextMail extends MailTemplate {
public TextMail(String from, String to, String subject, String context) {
super(from, to, subject, context);
}
public String getContext() {
String context = "\nContext-Type:text/plain;charset=GB2312\n" + super.getContext();
context = context + "\n郵件格式爲:文本格式";
return context;
}
}
添加抽象郵件服務器及兩種郵件服務器的實現:
import com.sfq.impl.MailTemplate;
public abstract class MailServer {
protected final MailTemplate mailTemplate;
public MailServer(MailTemplate mailTemplate) {
this.mailTemplate = mailTemplate;
}
public void sendMail() {
System.out.println("------發送郵件-----");
System.out.println("發件人:" + mailTemplate.getFrom());
System.out.println("收件人:" + mailTemplate.getTo());
System.out.println("郵件標題:" + mailTemplate.getSubject());
System.out.println("郵件內容:" + mailTemplate.getContext());
}
}
import com.sfq.impl.MailServer;
import com.sfq.impl.MailTemplate;
public class Postfix extends MailServer {
public Postfix(MailTemplate mailTemplate) {
super(mailTemplate);
}
//修正郵件發送程序
public void sendMail() {
//增加郵件服務器信息
String context = "Received:from XXXX (unknow [xxx.xxx.xxx.xxx]) by "
+ "aaa.aaa.com (Postfix) with ESMTP id 8DBCD172B8\n";
super.mailTemplate.add(context);
super.sendMail();
}
}
import com.sfq.impl.MailServer;
import com.sfq.impl.MailTemplate;
public class SendMail extends MailServer {
public SendMail(MailTemplate mailTemplate) {
super(mailTemplate);
}
//修正郵件發送程序
public void sendMail() {
//增加郵件服務器信息
String context = "Received:(sendmail); 17 May 2020 22:54:54 +0100";
super.mailTemplate.add(context);
super.sendMail();
}
}
import com.sfq.action.HtmlMail;
import com.sfq.action.Postfix;
import com.sfq.impl.MailServer;
import com.sfq.impl.MailTemplate;
public class Client {
public static void main(String[] args) {
MailTemplate textMail = new HtmlMail("[email protected]", "[email protected]", "特大驚喜", "本店清倉,全場兩折!");
MailServer mailServer = new Postfix(textMail);
mailServer.sendMail();
}
}
結果
------發送郵件-----
發件人:[email protected]
收件人:[email protected]
郵件標題:特大驚喜
郵件內容:
Context-Type:multipart/mixed;charset=GB2312
Received:from XXXX (unknow [xxx.xxx.xxx.xxx]) by aaa.aaa.com (Postfix) with ESMTP id 8DBCD172B8
本店清倉,全場兩折!
郵件格式爲:超文本格式
中介者模式 VS 門面模式
門面模式:爲複雜的子系統提供一個統一的訪問界面,它定義的是一個高層接口,該接口使得子系統更加容易使用,避免外部模塊深入到子系統內部而產生與子系統內部細節耦合的問題。
中介者模式:使用一箇中介對象來封裝一系列同事對象的交互行爲,它使各對象之間不再顯式地引用,從而使其耦合鬆散,建立一個可擴展的應用架構。
主要區別:
★ 功能區別
- 門面模式:只是增加了一個門面,它對子系統來說沒有增加任何的功能,子系統若脫離門面模式是完全可以獨立運行的。
- 中介者模式:增加了業務功能,它把各個同事類中的原有耦合關係移植到了中介者,同事類不可能脫離中介者而獨立存在,除非是想增加系統的複雜性和降低擴展性。
★ 知曉狀態不同
- 門面模式:子系統不知道有門面存在。
- 中介者模式:每個同事類都知道中介者存在,因爲要依靠中介者調和同事之間的關係。
★ 封裝程度不同
- 門面模式:所有的請求處理都委託給子系統完成。
- 中介者模式:需要有一箇中心,由中心協調同事類完成,並且中心本身也完成部分業務,它屬於更進一步的業務功能封裝。
(1)中介者模式
假設工資與職位、稅收有關,職位提升工資就會增加,同時稅收也增加。而如果稅收比率增加,工資自然就減少。職位、稅收和工資三者之間兩兩都有關係。
//抽象同事類
public abstract class AbsColleague {
//每個同事都知道中介者
protected AbsMediator mediator;
public AbsColleague(AbsMediator mediator) {
this.mediator = mediator;
}
}
//職位
public interface IPosition {
public void promote(); //升職
public void demote(); //降職
}
import com.sfq.impl.AbsColleague;
import com.sfq.impl.AbsMediator;
import com.sfq.impl.IPosition;
public class Position extends AbsColleague implements IPosition {
public Position(AbsMediator mediator) {
super(mediator);
}
@Override
public void promote() {
super.mediator.up(this);
}
@Override
public void demote() {
super.mediator.down(this);
}
}
//工資
public interface ISalary {
public void increaseSalary(); //加新
public void decreaseSalary(); //降薪
}
import com.sfq.impl.AbsColleague;
import com.sfq.impl.AbsMediator;
import com.sfq.impl.ISalary;
public class Salary extends AbsColleague implements ISalary {
public Salary(AbsMediator mediator) {
super(mediator);
}
@Override
public void increaseSalary() {
super.mediator.up(this);
}
@Override
public void decreaseSalary() {
super.mediator.down(this);
}
}
//稅收
public interface ITax {
public void raise(); //升稅
public void drop(); //降稅
}
import com.sfq.impl.AbsColleague;
import com.sfq.impl.AbsMediator;
import com.sfq.impl.ITax;
public class Tax extends AbsColleague implements ITax {
public Tax(AbsMediator mediator) {
super(mediator);
}
@Override
public void raise() {
super.mediator.up(this);
}
@Override
public void drop() {
super.mediator.down(this);
}
}
//黑中介
import com.sfq.action.Position;
import com.sfq.action.Salary;
import com.sfq.action.Tax;
public abstract class AbsMediator {
protected final ISalary salary; //工資
protected final IPosition position;//職位
protected final ITax tax;//稅收
public AbsMediator() {
salary = new Salary(this);
position = new Position(this);
tax = new Tax(this);
}
public abstract void up(IPosition position); //升職
public abstract void down(IPosition position); //降職
public abstract void up(ISalary salary); //加新
public abstract void down(ISalary salary); //降薪
public abstract void up(ITax tax); //升稅
public abstract void down(ITax tax); //降稅
}
import com.sfq.action.Mediator;
import com.sfq.action.Position;
import com.sfq.action.Salary;
import com.sfq.action.Tax;
import com.sfq.impl.IPosition;
import com.sfq.impl.ISalary;
import com.sfq.impl.ITax;
public class Client {
public static void main(String[] args) {
Mediator mediator = new Mediator();
IPosition position = new Position(mediator);
ISalary salary = new Salary(mediator);
ITax tax = new Tax(mediator);
System.out.println("-----職位提升-----");
position.promote();
System.out.println("-----工資提升-----");
salary.increaseSalary();
System.out.println("-----稅收提升-----");
tax.raise();
}
}
結果
-----職位提升-----
職位上升!
工資加倍!
稅收增加!
-----工資提升-----
工資加倍!
稅收增加!
-----稅收提升-----
稅收增加!
工資縮減!
通過中介者模式,每個同事類的職位、工資、稅收都只與中介者通信,中介者封裝了各個同事類之間的邏輯關係,方便系統的擴展和維護。
(2)門面模式
對外界的訪問者來說,它只要傳遞進去一個人員名稱和月份即可獲得工資數,而不用關心其中的計算有多麼複雜,這就需要門面模式。門面模式對子系統起封裝作用,它可以提供一個統一的對外服務接口。
//出勤
import java.util.Random;
public class Attendance {
//得到出勤天數
public int getWorkDays() {
return (new Random()).nextInt(30); //隨機出勤天數
}
}
//獎金
public class Bonus {
private Attendance attendance = new Attendance();
public int getBonus() {
int workDays = attendance.getWorkDays(); //獲得出勤情況
int bonus = workDays * 1800 / 30; //計算獎金
return bonus;
}
}
//基本工資
public class BasicSalary {
public int getBasicSalary() {
return 2000;
}
}
import java.util.Random;
//績效
public class Performance {
private BasicSalary salary = new BasicSalary(); //基本工資
//績效獎勵
public int getPerformanceValue() {
int perf = (new Random()).nextInt(100); //隨機績效
return salary.getBasicSalary() * perf / 100;
}
}
import java.util.Random;
//稅
public class Tax {
public int getTax() {
return (new Random()).nextInt(300); //隨機稅金
}
}
public class SalaryProvider {
private BasicSalary basicSalary = new BasicSalary(); //基本工資
private Bonus bonus = new Bonus(); //獎金
private Performance perf = new Performance(); //績效
private Tax tax = new Tax(); //稅
public int totalSalary() {
return basicSalary.getBasicSalary() + bonus.getBonus() + perf.getPerformanceValue() - tax.getTax();
}
}
import java.util.Date;
//門面
public class HRFacade {
private SalaryProvider salaryProvider = new SalaryProvider();//總工資
private Attendance attendance = new Attendance();//考勤
public int querySalary(String name,Date date) {//查詢員工總收入
return salaryProvider.totalSalary();
}
public int queryWorkDays(String name) {//查詢員工上了幾天班
return attendance.getWorkDays();
}
}
import java.util.Date;
import com.sfq.action.HRFacade;
public class Client {
public static void main(String[] args) {
//門面
HRFacade facade = new HRFacade();
System.out.println("-----外系統查詢總收入-----");
int salary = facade.querySalary("張三", new Date(System.currentTimeMillis()));
System.out.println("張三 5月 總收入爲:" + salary);
System.out.println("-----外系統查詢出勤天數-----");
int workDays = facade.queryWorkDays("李四");
System.out.println("李四 本月出勤:" + workDays);
}
}
結果
-----外系統查詢總收入-----
張三 5月 總收入爲:2527
-----外系統查詢出勤天數-----
李四 本月出勤:3
使用了門面模式對薪水計算子系統進行封裝,避免子系統內部複雜邏輯外泄,確保子系統的業務邏輯的單純性,即使業務流程需要變更,影響的也是子系統內部功能,比如獎金需要與基本工資掛鉤,這樣的修改對外系統來說是透明的,只需要子系統內部變更即可。