设计模式总览
适配器模式
- 概述
- 适配器模式(Adapter Pattern) :将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作。
- 适配器模式分为类结构型模式(继承)和对象结构型模式(组合)两种,前者(继承)类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。
- 定义中所提及的接口是指广义的接口,它可以表示一个方法或者方法的集合
- 模式结构
- Target:目标抽象类
- Adapter:适配器类
- Adaptee:适配者类
类适配器
Adapter和Adaptee
是继承关系,对象适配器是关联关系
- 实例
使用一个加密适配器,通过适配不同的加密类,来实现不同的加密效果;使用对象适配器
/**
* @author lhj
* @create 2022/4/13 20:43
* 对象适配器和类适配器的区别
* 1、对象适配器通过委派与adaptee衔接,即持有adaptee对象,是动态的方式;类适配器通过集成与adaptee衔接,也就是说类适配器继承adaptee,并且实现target方法,是静态的方式。
*
* 2、由于对象适配器采用动态的方式与adaptee衔接,使得它可以对不同的适配源及其子类进行适配
*
* 3、类适配器可以重定义实现行为,而对象适配器重定义适配的行为比较困难,但是添加行为较方便。
*
* 尽量使用对象适配器的实现方式,多用合成/聚合、少用继承。
*
*/
public class Client {
public static void main(String[] args) {
Caesar c = new Caesar();
DataOperation dao = new CipherAdapter(c);
String pattern = dao.doEncrypt(9, "duoduo");
System.out.println("明文为:"+"duoduo");
System.out.println("密文为:"+pattern);
}
}
/**
* @author lhj
* @create 2022/4/13 20:31
* 数据操作类
*/
public interface DataOperation {
//encrypt:加密
String doEncrypt(int key, String email);
}
/**
* @author lhj
* @create 2022/4/13 20:41
* 加密适配类
*/
public class CipherAdapter implements DataOperation{
//cipher:密码
private Caesar cipher;
public CipherAdapter(Caesar caesar) {
this.cipher = caesar;
}
@Override
public String doEncrypt(int key, String email) {
return cipher.doEncrypt(key, email);
}
}
/**
* @author lhj
* @create 2022/4/13 20:33
* 数据加密类:通过26个字母的位运算来实现加密运算
*/
public final class Caesar {
public String doEncrypt(int key, String email){
String pattern = "";
for (int i = 0; i < email.length(); i++) {
char ch = email.charAt(i);
if(ch >= 'a' && ch <= 'z'){
ch += key % 26;
if(ch > 'z')
ch -= 26;
if(ch < 'a')
ch += 26;
}
if(ch >= 'A' && ch <= 'Z'){
ch += key % 26;
if(ch > 'Z')
ch -= 26;
if(ch < 'A')
ch += 26;
}
pattern += ch;
}
return pattern;
}
}
- 优点
- 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构
- 增加了类的透明性和复用性,提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用
- 灵活性和扩展性非常好
- 类适配器模式:置换一些适配者的方法很方便
- 对象适配器模式:可以把多个不同的适配者适配到同一个目标,还可以适配一个适配者的子类
- 缺点
类适配器模式:
- (1) 一次最多只能适配一个适配者类,不能同时适配多个适配者
- (2) 适配者类不能为最终类
- (3) 目标抽象类只能为接口,不能为类
对象适配器模式:在适配器中置换适配者类的某些方法比较麻烦
桥接模式
- 概述
- 桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。
- 对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式
- 用抽象关联取代了传统的多层继承
- 将类之间的静态继承关系转换为动态的对象组合关系
-
模式结构
-
实例
模拟毛笔的不同型号,以及每种型号的不同颜色
/**
* @author lhj
* @create 2022/6/28 17:48
*/
public class BridgeTest {
public static void main(String[] args) {
Pen big = new BigPen();
big.setColor(new Blue());
big.draw("房子");
}
}
/**
* 抽象的毛笔类
*/
abstract class Pen{
//颜色
public Color color;
//毛笔名字
public String name;
public Pen(String name) {
this.name = name;
}
public void setColor(Color color) {
this.color = color;
}
public abstract void draw(String name);
}
class SmallPen extends Pen{
public SmallPen() {
super("小号笔");
}
@Override
public void draw(String name) {
color.beginPaint(this.name, name);
}
}
class BigPen extends Pen{
public BigPen() {
super("大号笔");
}
@Override
public void draw(String name) {
color.beginPaint(this.name, name);
}
}
interface Color{
void beginPaint(String penType, String name);
}
class Red implements Color{
@Override
public void beginPaint(String penType, String name) {
System.out.println(name+penType+"紅色");
}
}
class Blue implements Color{
@Override
public void beginPaint(String penType, String name) {
System.out.println(name+penType+"藍色");
}
}
- 优点
- 分离抽象接口及其实现部分
- 可以取代多层继承方案,极大地减少了子类的个数
- 提高了系统的可扩展性,在两个变化维度中任意扩展一个维度,不需要修改原有系统,符合开闭原则
- 缺点
- 会增加系统的理解与设计难度,由于关联关系建立在抽象层,要求开发者一开始就要针对抽象层进行设计与编程
- 正确识别出系统中两个独立变化的维度并不是一件容易的事情
装饰器模式
- 概述
- 动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活
- 对象结构型模式
- 以对客户透明的方式动态地给一个对象附加上更多的责任
- 可以在不需要创建更多子类的情况下,让对象的功能得以扩展
装饰器和适配器的区别在于:
- 适配器是连接两个类,增强一个类
- 装饰器是增强一个类
- 透明装饰模式
- 透明
(Transparent)
装饰模式:要求客户端完全针对抽象编程,装饰模式的透明性要求客户端程序不应该将对象声明为具体构件类型或具体装饰类型,而应该全部声明为抽象构件类型- 对于客户端而言,具体构件对象和具体装饰对象没有任何区别
- 可以让客户端透明地使用装饰之前的对象和装饰之后的对象,无须关心它们的区别
- 可以对一个已装饰过的对象进行多次装饰,得到更为复杂、功能更为强大的对象
- 无法在客户端单独调用新增方法
addedBehavior()
- 不透明装饰模式
- 半透明
(Semi-transparent)
装饰模式:用具体装饰类型来定义装饰之后的对象,而具体构件使用抽象构件类型来定义- 对于客户端而言,具体构件类型无须关心,是透明的;但是具体装饰类型必须指定,这是不透明的
- 可以给系统带来更多的灵活性,设计相对简单,使用起来也非常方便
- 客户端使用具体装饰类型来定义装饰后的对象,因此可以单独调用
addedBehavior()
方法- 最大的缺点在于不能实现对同一个对象的多次装饰,而且客户端需要有区别地对待装饰之前的对象和装饰之后的对象
- 实例
/**
* @author lhj
* @create 2022/4/13 21:58
*/
public class DecoratorTest {
public static void main(String[] args) {
ReportGenerationTool reportGenerationTool = new Addition();
reportGenerationTool.change();
}
}
/**
* @author lhj
* @create 2022/4/13 22:03
* 报表类,被装饰类
*/
public class Report{
public void who(){
System.out.println("我是一张没有头尾的报表");
}
}
/**
* @author lhj
* @create 2022/4/13 22:02
* 抽象构建类,报表首尾生成器
*/
public interface ReportGenerationTool {
public void change();
}
/**
* @author lhj
* @create 2022/4/13 22:06
* 具体装饰类,添加
*/
public class Addition implements ReportGenerationTool{
private Report report;
public void addHeader(){
System.out.println("添加表头");
}
public void addTail(){
System.out.println("添加表尾");
}
@Override
public void change() {
report.who();
addHeader();
addTail();
}
}
- 优点
- 对于扩展一个对象的功能,装饰模式比继承更加灵活,不会导致类的个数急剧增加
- 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的具体装饰类,从而实现不同的行为
- 可以对一个对象进行多次装饰
- 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,且原有类库代码无须改变,符合开闭原则
- 缺点
- 使用装饰模式进行系统设计时将产生很多小对象,大量小对象的产生势必会占用更多的系统资源,在一定程度上影响程序的性能
- 比继承更加易于出错,排错也更困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐
代理模式
- 概述
- 给某一个对象提供一个代理,并由代理对象控制对原对象的引用
- 对象结构型模式
- 代理对象可以在客户端和目标对象之间起到中介的作用
- 通过代理对象去掉客户不能看到的内容和服务或者添加客户需要的额外的新服务
- 模式结构
- 常见代理模式
- 远程代理
(Remote Proxy)
:为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以在同一台主机中,也可以在另一台主机中,远程代理又称为大使(Ambassador)- 虚拟代理
(Virtual Proxy)
:如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建- 保护代理
(Protect Proxy)
:控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限- 缓冲代理
(Cache Proxy)
:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果- 智能引用代理
(Smart Reference Proxy)
:当一个对象被引用时,提供一些额外的操作,例如将对象被调用的次数记录下来等
- 常用代理模式
一个是静态代理,一个是动态代理;这里我们就简单的写一个静态代理,对于动态代理我在这里有详解
例如我们协议个黄牛代抢票的例子:
/**
* @author lhj
* @create 2022/6/28 18:54
*/
public class StaticProxyTest {
public static void main(String[] args) {
TicketWindow me = new Me();
TicketWindow proxy = new ProxyMe(me);
proxy.getTicket();
}
}
interface TicketWindow{
void getTicket();
}
class Me implements TicketWindow{
@Override
public void getTicket() {
System.out.println("拿到票啦");
}
}
/**
* 黄牛,即代理对象
*/
class ProxyMe implements TicketWindow{
private TicketWindow me;
public ProxyMe(TicketWindow me) {
this.me = me;
}
@Override
public void getTicket() {
System.out.println("自己买票也就图一乐,要抢票还得看我");
me.getTicket();
}
}
- 优点
- 能够协调调用者和被调用者,在一定程度上降低了系统的耦合度
- 客户端可以针对抽象主题角色进行编程,增加和更换代理类无须修改源代码,符合开闭原则,系 统具有较好的灵活性和可扩展性
- 远程代理:可以将一些消耗资源较多的对象和操作移至性能更好的计算机上,提高了系统的整体运行效率
- 虚拟代理:通过一个消耗资源较少的对象来代表一个消耗资源较多的对象,可以在一定程度上节省系统的运行开销
- 缓冲代理:为某一个操作的结果提供临时的缓存存储空间,以便在后续使用中能够共享这些结果,优化系统性能,缩短执行时间
- 保护代理:可以控制对一个对象的访问权限,为不同用户提供不同级别的使用权限
- 缺点
- 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢(例如保护代理)
- 实现代理模式需要额外的工作,而且有些代理模式的实现过程较为复杂(例如远程代理)
组合模式
- 概述
- 组合多个对象形成树形结构以表示“部分-整体”的结构层次。组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性。
- 对象结构型模式
- 将对象组织到树形结构中,可以用来描述整体与部分的关系
- 实例
package experiment.four.threeCombination;
/**
* @author lhj
* @create 2022/4/13 21:45
*/
public class Client {
public static void main(String[] args) {
MyElement one = new Group("one");
MyElement memberOne = new Member("memberOne");
MyElement memberTwo = new Member("memberTwo");
MyElement memberThree = new Member("memberThree");
MyElement memberFour = new Member("memberFour");
one.addMemberOrGroupToGroup(memberOne);
one.addMemberOrGroupToGroup(memberTwo);
one.addMemberOrGroupToGroup(memberThree);
one.addMemberOrGroupToGroup(memberFour);
System.out.println(((Member) memberOne).getMemberName()+"分享了一条动态");
memberOne.share(one);
}
}
/**
* @author lhj
* @create 2022/4/13 21:10
* 这个抽象类定义用户可以在群组中使用到的各种功能
*/
public abstract class MyElement {
public abstract void remove(MyElement element);
public abstract void addMemberOrGroupToGroup(MyElement element);
public abstract void share(MyElement element);
}
package experiment.four.threeCombination;
import java.util.ArrayList;
/**
* @author lhj
* @create 2022/4/13 21:31
*/
public class Group extends MyElement{
private String groupName;
public ArrayList<MyElement> elements = new ArrayList<>();
public String getGroupName() {
return groupName;
}
public void setGroupName(String groupName) {
this.groupName = groupName;
}
public Group(String groupName) {
this.groupName = groupName;
}
public ArrayList<MyElement> getElements(){
return elements;
}
@Override
public void remove(MyElement element) {
elements.remove(element);
}
@Override
public void addMemberOrGroupToGroup(MyElement element) {
elements.add(element);
}
@Override
public void share(MyElement element) {
if (element instanceof Group){
ArrayList<MyElement> com = ((Group)element).getElements();
for (MyElement object : elements){
object.share(object);
}
}else if(element instanceof Member){
((Member)element).onMessage();
}
}
}
package experiment.four.threeCombination;
import java.util.ArrayList;
/**
* @author lhj
* @create 2022/4/13 21:19
* 叶子结点类:用户
*/
public class Member extends MyElement{
private String memberName;
public Member(String memberName) {
this.memberName = memberName;
}
public String getMemberName() {
return memberName;
}
public void setMemberName(String memberName) {
this.memberName = memberName;
}
@Override
public void remove(MyElement element) {
System.out.println("无权限!");
}
@Override
public void addMemberOrGroupToGroup(MyElement element) {
System.out.println("无权限!");
}
@Override
public void share(MyElement element) {
if (element instanceof Group){
ArrayList<MyElement> components =((Group)element).getElements();
if (components.contains(this)){components.remove(this);}
for (MyElement obj:components){obj.share(obj);
}
components.add(this);
}else if (element instanceof Member){
((Member)element).onMessage();
}
}
public void onMessage() {
System.out.println(this.getMemberName()+"收到消息");
}
}
- 优点
- 可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,让客户端忽略了层次的差异,方便对整个层次结构进行控制
- 客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码
- 增加新的容器构件和叶子构件都很方便,符合开闭原则
- 为树形结构的面向对象实现提供了一种灵活的解决方案
- 缺点
在增加新构件时很难对容器中的构件类型进行限制
享元模式
- 概述
- 运用共享技术有效地支持大量细粒度对象的复用。
- 系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用
- 由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式
- 内部外部状态
- 内部状态
(Intrinsic State)
:存储在享元对象内部并且不会随环境改变而改变的状态,内部状态可以共享(例如:字符的内容)- 外部状态
(Extrinsic State)
:随环境改变而改变的、不可以共享的状态。享元对象的外部状态通常由客户端保存,并在享元对象被创建之后,需要使用的时候再传入到享元对象内部。一个外部状态与另一个外部状态之间是相互独立的(例如:字符的颜色和大小)
- 优点
- 可以减少内存中对象的数量,使得相同或者相似的对象在内存中只保存一份,从而可以节约系统资源,提高系统性能
- 外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享
- 缺点
- 使得系统变得复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化
- 为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长
外观模式
- 概述
- 外观(Facade)模式又叫作门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。
- 例如,孩子出生要去公安局班里户口,再去社保局交社保,再去学习办理入学等等,现在提供一个办公大楼,里面有所有办事处,就只去这个大楼就可以解决不需要new 三个类。
- 模式结构
- 优点
- 它对客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并使得子系统使用起来更加容易
- 它实现了子系统与客户端之间的松耦合关系,这使得子系统的变化不会影响到调用它的客户端,只需要调整外观类即可
- 一个子系统的修改对其他子系统没有任何影响,而且子系统的内部变化也不会影响到外观对象
- 缺点
- 不能很好地限制客户端直接使用子系统类,如果对客户端访问子系统类做太多的限制则减少了可变性和灵活性
- 如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了开闭原则