📖本篇内容:23种设计模式——结构性模型(上) 适配器模式 桥接模式 组合模式 📆 最近更新:2021年12月26日 23种设计模式 这栏文章让你拿捏得死死的 学不会来打我——构建型模式——工厂模式 抽象工厂模式 单例模式 建造者模式 原型模式 🙊个人简介:一只二本院校在读的大三程序猿,本着注重基础,打卡算法,分享技术作为个人的经验总结性的博文博主,虽然可能有时会犯懒,但是还是会坚持下去的,如果你很喜欢博文的话,建议看下面一行~(疯狂暗示QwQ) 🌇 点赞 👍 收藏 ?留言 📝 一键三连 关爱程序猿,从你我做起
写在前面
因为 🙊(猿小付)最近在考试所以时至一个星期后才有了23种设计模式的第 2 篇文章,这栏文章目的是为了快速入门了解 面向对象设计中的23种设计模式 ,所以可能没什么特别的深度,因为笔者能力也有限,只能给大家讲个大概,同时也是使得博主加深印象,如有疑问,遗漏,希望您能提出宝贵的建议,共同研究解决,一起进步~话不多说,接着上文。
前言
23种设计模式下的六大原则
无论何种设计模式,都是基于六大设计原则:
- 开放封闭原则:一个软件实体如类、
模块和函数应该对修改封闭,对扩展开放 。 - 单一职责原则:
一个类就干一件事 ,一个类应该有有一个引起它修改的原因。 - 里氏替换原则:子类应该可以完全替换掉父类。换句话来说就是在使用继承时,
只扩展新的功能,而不要破坏父类原有的功能 。 - 依赖倒置原则:细节应该依赖于抽象,抽象
不应该依赖于细节 。把抽象层放在程序设计的高层,并保持稳定,程序的细节变化由底层的实现层 来完成。 - 接口分离原则:客户端不该依赖它不需要的接口。如果一个接口在实现时,部分方法由于冗余被客户端空实现,则应该将接口拆分,让
实现类只需要以来自己需要的接口方法 。 - 迪米特法则:最少知道原则,尽量降低类与类之间的耦合;
一个对象应该对其他对象有最少的了解 。
结构型模型
适配器模式 Adapter
什么是适配器模式?
将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容的而不能在一起工作的那些类可以在一起工作。
它能解决什么问题?
即Adapter模式使得原本接口不兼容不能一起工作的类可以一起工作。
举个小例子~
大家肯定用到过现实中的读卡器,毕竟你相机里的照片想要读取到电脑中进行相对应的修改就需要用到读卡器,那咱们通常是不可能直接把SD卡直接插在电脑上进行读取,因为一般的电脑貌似都没有这个接口兼容读取SD卡,但是呢有个聪明的崽子发明了读卡器,读卡器最开始的思想就是适配器模式衍生而来的,因为SD卡与电脑不兼容,所以读卡器作为适配器接口,一边连接SD卡,一边连接电脑的USB这样就可以成功读取数据进行修图等操作了 。
模式中的角色?
- 目标接口(Target):客户所期待的接口。目标可以使具体的或抽象的类,也可以是接口。
- 需要适配的类(Adaptee):需要进行适配的类或者适配者类。
- 适配器(Adapter):通过包装一个需要适配的对象,把原接口转化为目标接口。
如何实现呢?
1、类的适配器模式(采用继承实现)
2、对象适配器(采用对象组合的方式实现)
类的适配器模式
SD卡需要进行适配为USB能读取到电脑的接口:
public class SDCard_Adaptee {
public void specificRequest(){
System.out.println("显示SD卡中的内容");
}
}
public interface USBInterface {
public void request();
}
public class USBTarget implements USBInterface{
@Override
public void request() {
System.out.println("USB接口 具有 读取含有USB接口的工具的功能");
}
}
public class CardReader_Adapter extends SDCard_Adaptee implements USBInterface{
@Override
public void request() {
super.specificRequest();
}
}
public class Test {
public static void main(String[] args) {
USBInterface usbTarget = new USBTarget();
usbTarget.request();
USBInterface cardReader_adapter = new CardReader_Adapter();
cardReader_adapter.request();
}
}
上面这种实现的适配器被称之为类适配器,因为Adapter类既继承了Adaptee(被适配的类),也实现了USBInterface接口(Java中不支持多继承,所以这样实现 ),在测试类中,我们客户可以根据需求选择创建任意一种符合要求的子类,来实现具体的功能。另外的适配器模式是对象适配器 ,他不是使用多继承或继承再实现的方式 ,而是直接关联 ,或者称之为委托的方法:
public class CardReader_Adapter implements USBInterface{
private SDCard_Adaptee adaptee ;
public CardReader_Adapter(SDCard_Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
this.adaptee.specificRequest();
}
}
public class Test {
public static void main(String[] args) {
USBInterface cardReader_adapter = new CardReader_Adapter(new SDCard_Adaptee());
cardReader_adapter.request();
}
}
测试结果相同,唯一的区别就是在于我们只不过修改了Adapter类的内部结构,使其自身拥有一个被适配类的对象 ,再把具体的特殊功能委托给这个对象实现 。使用对象适配器模式,可以根据传入的被适配器对象 达到适配多个不同被适配类的功能 。当然,此时我们可以为多个被适配类提取出一个接口或抽象类 。这样看起来的话,似乎对象适配器模式更加灵活一点。
实际开发案例
比如手机电源的适配器 (转接头)都用过的。
以及JDK中的Future Task实现了Runnable接口,组合了Callable,可以使用Callable与Thread结合。
桥接模式 Bridge
考虑一个需求:绘制矩形、原型、三角形、这三个图案。按照面向对象的理念,我们至少需要三个具体类,对应三种不同的图形。
抽象一个接口Shape:
public interface Shape {
public void draw();
}
三个具体的实现类:
public class Circle implements Shape{
@Override
public void draw() {
System.out.println("绘制一个圆形");
}
}
public class Rectangle implements Shape{
@Override
public void draw() {
System.out.println("绘制一个矩形");
}
}
public class Triangle implements Shape{
@Override
public void draw() {
System.out.println("绘制一个三角形");
}
}
如果此时增加了新的需求,每一种形状的图形都需要四种不同的颜色。
- 为了复用形状类,将每种形状定义为父类,每种不同颜色的图形继承自其形状父类。此时一共有 12 个类。
- 为了复用颜色类,将每种颜色定义为父类,每种不同颜色的图形继承自其颜色父类。此时一共有 12 个类。
但是这样一直下去,如果以后需要增加一种颜色,就需要增加三个类对应三种图形,增加一种形状就要增加五个类对应五种颜色。
形状和颜色,都是图形的两个属性。他们关系平等,不属于继承关系。更好的实现方式是:将形状与颜色分离,根据需要对形状和颜色进行组合 ,这就是桥接模式的思想。
桥接模式:将抽象部分与它的实现部分分离,是的它们都可以进行独立地变化。它是一种对象结构型模式,又被称之为柄体模式或者接口模式。
通俗来说,如果当一个对象有了两种或者多种分类方式 ,并且两种方式都容易变化 ,比如本例子中的形状与颜色。这个时候使用继承很容易造成子类越来越多 ,所以更好的做法是把这种分类方式分离出来 ,让他们独立变化,使用时将不同的分类进行组合即可。
让我们一起来看一下本例使用桥接模式的程序实现:
新建接口类 IColor,仅包含一个获取颜色的方法:
public interface IColor {
public String getColor();
}
public class Blue implements IColor {
@Override
public String getColor() {
return "蓝";
}
}
public class Red implements IColor{
@Override
public String getColor() {
return "红";
}
}
随后在每个形状中桥接接口:
public class Rectangle implements Shape{
private IColor color;
void setColor(IColor color){
this.color = color;
}
@Override
public void draw() {
System.out.println(color.getColor()+"绘制一个矩形");
}
}
public class Triangle implements Shape{
private IColor color;
void setColor(IColor color){
this.color = color;
}
@Override
public void draw() {
System.out.println(color.getColor()+"绘制一个三角形");
}
}
public class Circle implements Shape{
private IColor color;
void setColor(IColor color){
this.color = color;
}
@Override
public void draw() {
System.out.println(color.getColor()+"绘制一个圆形");
}
}
测试用例:
public class Test {
public static void main(String[] args) {
Circle circle = new Circle();
circle.setColor(new Blue());
circle.draw();
}
}
桥接模式的重点在于:将抽象部分与它的实现部分分离 ,使它们都可以独立地变化 。抽象部分指的是父类,对应本例中的形状类,实现部分指的是不同子类的区别之处 。将子类的区别方式 —— 也就是本例中的颜色 —— 分离成接口 ,通过组合的方式桥接颜色和形状,这就是桥接模式,它主要用于两个或多个同等级的接口 。
组合模式 Composite
组合模式是什么?干什么用的?
组合模式又被称之为 部分整体模式 ,是用于把一组相似的对象当做一个单一的对象。组合模式依赖于树形结构来组合对象 ,用来表示部分以及整体层次 。这种类型的设计模式属于结构型模式,因为它创建了对象组的树形结构。
组合模式用于整体与部分的结构,当整体与部分有相似的结构,在操作时可以被一致对待时,就可以使用组合模式。
-
文件夹和子文件夹的关系:文件夹中可以存放文件,也可以新建文件夹,子文件夹也一样。 -
总公司子公司的关系:总公司可以设立部门,也可以设立分公司,子公司也一样。 -
树枝和分树枝的关系:树枝可以长出叶子,也可以长出树枝,分树枝也一样。
考虑一个实际的应用场景,设计一个公司的人员分布结构,结构如图所示:
我们注意到人员结构中有两种结构,一是管理者,如老板,PM,CFO,CTO,二是职员。其中有的管理者不仅仅要管理职员,还会管理其他的管理者。这就是一个典型的整体与部分的结构。
不使用组合模式时的设计方式
管理者类:
package com.alascanfu.CompositePattern;
import java.util.ArrayList;
import java.util.List;
public class Manager {
private String position;
private String job;
private List<Manager> managers = new ArrayList<>();
private List<Employee> employees = new ArrayList<>();
public Manager(String position, String job) {
this.position = position;
this.job = job;
}
public void addManager(Manager manager){
managers.add(manager);
}
public void removeManager(Manager manager){
managers.remove(manager);
}
public void addEmployee(Employee employee){
employees.add(employee);
}
public void removeEmployee(Employee employee){
employees.remove(employee);
}
public void work(){
System.out.println("我是"+position+"正在"+job);
}
public void check() {
work();
for (Employee employee : employees) {
employee.work();
}
for (Manager manager : managers) {
manager.check();
}
}
}
员工类:
package com.alascanfu.CompositePattern;
public class Employee {
private String position;
private String job;
public Employee(String position, String job) {
this.position = position;
this.job = job;
}
public void work() {
System.out.println("我是" + position + ",我正在" + job);
}
}
测试类:
package com.alascanfu.CompositePattern;
public class Test {
public static void main(String[] args) {
Manager boss = new Manager("老板", "唱怒放的生命");
Employee HR = new Employee("人力资源", "聊微信");
Manager PM = new Manager("产品经理", "不知道干啥");
Manager CFO = new Manager("财务主管", "看剧");
Manager CTO = new Manager("技术主管", "划水");
Employee UI = new Employee("设计师", "画画");
Employee operator = new Employee("运营人员", "兼职客服");
Employee webProgrammer = new Employee("程序员", "学习设计模式");
Employee backgroundProgrammer = new Employee("后台程序员", "CRUD");
Employee accountant = new Employee("会计", "背九九乘法表");
Employee clerk = new Employee("文员", "给老板递麦克风");
boss.addEmployee(HR);
boss.addManager(PM);
boss.addManager(CFO);
PM.addEmployee(UI);
PM.addManager(CTO);
PM.addEmployee(operator);
CTO.addEmployee(webProgrammer);
CTO.addEmployee(backgroundProgrammer);
CFO.addEmployee(accountant);
CFO.addEmployee(clerk);
boss.check();
}
}
这样的结构设计存在两个缺点:
- name 字段,job字段,work方法重复了。
- 管理者对于其管理的管理者以及职员需要进行区别对待。
组合模式如何设计?
组合模式最主要就是让所有人一致对待整体和部分结构,将二者作为一个组件,执行不同的工作,先将组件进行抽象。
透明方式
组件抽象类:
package com.alascanfu.CompositePattern;
public abstract class Component {
private String position;
private String job;
public Component(String position, String job) {
this.position = position;
this.job = job;
}
public void work() {
System.out.println("我是" + position + ",我正在" + job);
}
abstract void addComponent(Component component);
abstract void removeComponent(Component component);
abstract void check();
}
管理员继承此抽象类:
package com.alascanfu.CompositePattern;
import java.util.ArrayList;
import java.util.List;
public class Manager extends Component{
private List<Component> components = new ArrayList<>();
public Manager(String position, String job) {
super(position, job);
}
@Override
void addComponent(Component component) {
components.add(component);
}
@Override
void removeComponent(Component component) {
components.remove(component);
}
@Override
void check() {
work();
for (Component component : components){
component.check();
}
}
}
员工也继承此抽象类:
package com.alascanfu.CompositePattern;
public class Employee extends Component{
public Employee(String position, String job) {
super(position, job);
}
@Override
void addComponent(Component component) {
System.out.println("员工没有管理权限");
}
@Override
void removeComponent(Component component) {
System.out.println("员工没有管理权限");
}
@Override
void check() {
work();
}
}
测试代码:
package com.alascanfu.CompositePattern;
public class Test {
public static void main(String[] args) {
Component boss = new Manager("老板", "唱怒放的生命");
Component HR = new Employee("人力资源", "聊微信");
Component PM = new Manager("产品经理", "不知道干啥");
Component CFO = new Manager("财务主管", "看剧");
Component CTO = new Manager("技术主管", "划水");
Component UI = new Employee("设计师", "画画");
Component operator = new Employee("运营人员", "兼职客服");
Component webProgrammer = new Employee("程序员", "学习设计模式");
Component backgroundProgrammer = new Employee("后台程序员", "CRUD");
Component accountant = new Employee("会计", "背九九乘法表");
Component clerk = new Employee("文员", "给老板递麦克风");
boss.addComponent(HR);
boss.addComponent(PM);
boss.addComponent(CFO);
PM.addComponent(UI);
PM.addComponent(CTO);
PM.addComponent(operator);
CTO.addComponent(webProgrammer);
CTO.addComponent(backgroundProgrammer);
CFO.addComponent(accountant);
CFO.addComponent(clerk);
boss.check();
}
}
可以看到,使用了组合模式之后,解决了之前的两个缺点。一是将公有的字段与方法移到了父类当中,消除了重复,并且在测试用例中,可以一致对待Manager和Employee类:
- Manager类和Employee类统一声明了Component的对象
- 统一调用了Component对象的addComponent方法添加了子对象
但是这样也存在问题 你是否发现在Employee类中违反了接口隔离原则!
接口隔离原则:客户端不该依赖它不需要的接口。如果一个接口在实现时,部分方法由于冗余被客户端空实现,则应该将接口拆分,让实现类只需要以来自己需要的接口方法 。
所以组合模式中的改进——安全方式与透明方式
透明方式
透明方式:在Component中声明所有的管理子对象的方法,包括了add与remove等,这样继承其子类都有了这些方法,对于外界来说叶子节点和枝干节点都是透明的,具备了完全一致的接口。
但他的缺点就出现了:违背了接口分离原则,而且当测试用例去调用相应方法的时候,可能导致程序出错,所以这种方式不安全。
那我们可以进行改进:将这两个无需在Employee中出现的方法移步至Manager类中单独实现,去掉抽象类中的方法。
修改如下:
public abstract class Component {
private String position;
private String job;
public Component(String position, String job) {
this.position = position;
this.job = job;
}
public void work() {
System.out.println("我是" + position + ",我正在" + job);
}
abstract void check();
}
修改Manager类:
public class Manager extends Component {
private List<Component> components = new ArrayList<>();
public Manager(String position, String job) {
super(position, job);
}
public void addComponent(Component component) {
components.add(component);
}
void removeComponent(Component component) {
components.remove(component);
}
@Override
public void check() {
work();
for (Component component : components) {
component.check();
}
}
}
其单独实现了具有权限的添加员工与移除员工两个方法。
Employee类修改如下:
public class Employee extends Component {
public Employee(String position, String job) {
super(position, job);
}
@Override
void check() {
work();
}
}
如上这些方式就被称之为组合模式的安全方式。
安全方式:在 Component 中不声明 add 和 remove 等管理子对象的方法,这样叶节点就无需实现它,只需在枝节点中实现管理子对象的方法即可。
问题:组合模式中的安全方式与透明方式有什么区别?
关键点在于:透明模式与安全模式。透明违背了接口隔离原则(即全部继承接口方法)。而安全模式遵循了接口隔离原则。
写在最后
这就是本次介绍的结构性模型中的适配器模式,桥接模式与组合模式,在实际开发当中,适配器模式应用的也是比较多的,开发者可以根据业务需求进行自己的设计适配器模式。 最后本文是参阅力扣官网《深入浅出23种设计模式》一书,如需更加了解请移步至官网查看~
最后
每天进步点 每天收获点
愿诸君 事业有成 学有所获
如果觉得不错 别忘啦一键三连哦~
|