策略模式 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("a@a.com", "b@a.com", "特大惊喜", "本店清仓,全场两折!");
MailServer mailServer = new MailServer(textMail);
mailServer.sendMail();
}
}
结果
------发送邮件-----
发件人:a@a.com
收件人:b@a.com
邮件标题:特大惊喜
邮件内容:
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("a@a.com", "b@a.com", "特大惊喜", "本店清仓,全场两折!");
MailServer mailServer = new Postfix(textMail);
mailServer.sendMail();
}
}
结果
------发送邮件-----
发件人:a@a.com
收件人:b@a.com
邮件标题:特大惊喜
邮件内容:
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
使用了门面模式对薪水计算子系统进行封装,避免子系统内部复杂逻辑外泄,确保子系统的业务逻辑的单纯性,即使业务流程需要变更,影响的也是子系统内部功能,比如奖金需要与基本工资挂钩,这样的修改对外系统来说是透明的,只需要子系统内部变更即可。