23种设计模式归纳总结——结构型_化作孤岛的瓜的博客-爱代码爱编程
目录
主要解决“类或对象的组合或组装”问题,将不同功能代码解耦
5.代理模式
在不直接修改原有类的前提下对原有类的方法功能进行扩展。
通过引入代理类(构参带入)来给原始类附加功能
示例:
定义一个绘制api DrawApi, 需要在绘制前后进行一些扩展功能(打点,监控等)
1.静态代理
缺点:
1:如果原始类有多个方法,我们需要在代理类中,将原始类中的所有的方法,都重新实现一遍
2:如果存在多个被代理类(比如下面的绘制 和 测算两个类) 都需要通过代理模式添加特殊功能,
则需要针对每个被代理类都创建一个代理类
//定义一个绘制接口(原始类)
interface DrawApi {
void draw();
void refresh();
}
//定义具体的圆形绘制方案,被代理类
static class CircleDraw implements DrawApi {
@Override
public void draw() {
System.out.println("画一个圆");
}
@Override
public void refresh() {
System.out.println("刷新");
}
}
//定义一个测算接口,原始类
interface CalculateApi {
void calculate();
}
//定义一个绘制代理类,代理实现具体的绘制功能,代理类
static class DrawProxy implements DrawApi {
//被代理的绘制方案
private DrawApi target;
//构造器
public DrawProxy() {
this.target = new CircleDraw();
}
@Override
public void draw() {
before();
target.draw();
after();
}
@Override
public void refresh() {
//缺点:我们需要在代理类中,将原始类中的所有的方法,都重新实现一遍
}
private void after() {
System.out.println("执行后 做一些事情");
}
private void before() {
System.out.println("执行前 做一些事情");
}
}
public static void main(String[] args) {
//使用静态代理
new DrawProxy().draw();
// 如果需要扩展其他原始类,则这样行不通
//new DrawProxy(new CalculateApi()).draw();
//线程也用到了代理模式,传入不同的runnable实现
//new Thread(实现runnable接口的实例化对象).start();
}
2.动态代理
主要是为了解决静态代理的两个缺点
//定义一个绘制接口,原始类
interface DrawApi {
void draw();
void refresh();
}
//定义具体的圆形绘制方案,被代理类
static class CircleDraw implements DrawApi {
@Override
public void draw() {
System.out.println("画一个圆");
}
@Override
public void refresh() {
System.out.println("刷新");
}
}
//定义一个测算接口,原始类
interface CalculateApi {
void calculate();
}
//定义圆形的测算方案,被代理类
static class CircleCalculate implements CalculateApi {
@Override
public void calculate() {
System.out.println("测量圆的尺寸");
}
}
//动态代理实现类
//优点:不需要将将原始类中的所有的方法,都重新实现一遍
public static class DrawProxy {
public DrawProxy() {
}
public Object createProxy(Object proxiedObject) {
Class<?>[] interfaces = proxiedObject.getClass().getInterfaces();
DynamicProxyHandler handler = new DynamicProxyHandler(proxiedObject);
return Proxy.newProxyInstance(proxiedObject.getClass()
.getClassLoader(), interfaces, handler);
}
//代理类的实现处理接口
private class DynamicProxyHandler implements InvocationHandler {
private final Object drawImpl;
public DynamicProxyHandler(Object drawImpl) {
this.drawImpl = drawImpl;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(drawImpl, args);
String apiName = drawImpl.getClass().getName() + ":" + method.getName();
System.out.println("执行方法:" + apiName);
after();
return result;
}
private void after() {
System.out.println("执行后 做一些事情");
}
private void before() {
System.out.println("执行前 做一些事情");
}
}
}
public static void main(String[] args) {
DrawProxy proxy = new DrawProxy();
DrawApi drawApi = (DrawApi) proxy.createProxy(new CircleDraw());
drawApi.draw();
//完美解决 原始类有多个的问题
CalculateApi calculateApi = (CalculateApi) proxy.createProxy(new CircleCalculate());
calculateApi.calculate();
}
6.桥接模式
概念:
与代理模式容易混淆。一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展。
通俗的说,就是一个类如果有多个属性需要变化,在进行扩展(继承)时会导致新增的子类指数增加,所以需要通过桥接模式将继承改为组合的方式,
将属性的维度解藕出来,将抽象与原本复杂的实现分离,提高扩展能力。
区别:
与代理模式相比,假设将一个纬度抽象以后,执行的方法命名为a()
代理模式侧重于 通过代理的方式扩展被代理类的功能
桥接模式侧重于 通过组合的方式让不同的纬度的原始类可以搭配使用
示例:
以Android画图举例,需要提供一个接口DrawApi,可以绘制三种颜色,与三种形状,普通的做法是通过子类实现不同的组合,一共有9种子类。
这里把颜色解藕然后通过构参带入,把形状解藕为通过子类实现,使其变为两个自由的纬度,通过组合关系(也就是桥梁)任意组合在一起,
可以大幅减少实现的类型。
//颜色抽象化,原始类
interface Color {
String getColor();
}
//具体的红色,原始类实现
static class RedColor implements Color {
@Override
public String getColor() {
return "红色";
}
}
//创建桥接的实现接口。画图api
static abstract class DrawApi {
protected Color mColor;
public DrawApi(final Color color) {
mColor = color;
}
abstract void draw();
}
//桥接模式实现类
static class CircleDraw extends DrawApi {
public CircleDraw(final Color color) {
super(color);
}
@Override
void draw() {
System.out.println("画一个 " + mColor.getColor() + " 的圆形");
}
}
public static void main(String[] args) {
DrawApi drawApi = new CircleDraw(new RedColor());
drawApi.draw();
}
7.装饰器模式
概念:
装饰器模式是指给一个类增强一些方法,对其做一些包装,但是不会影响改变原本类。
异同:
1.与代理模式的区别
装饰器模式看起来跟代理模式特别的相似,但是
对于代理模式来说可以作出一些操作改变原有代码,也就是说带有侵入性。
装饰者模式侧重于添加添加额外的功能职责,也可以重叠使用。
2.与继承的区别:
继承设计子类,是在编译时静态决定的,通过组合的做法扩展对象,可以在运行时动态地进行扩展
装饰者模式通过组合和委托,可以在运行时动态地为对象加上新的行为
示例:
需要一个画图api,在draw之后,需要设置颜色和形状
//画图api抽象类
static abstract class DrawComponent {
abstract void draw();
}
static class DrawApi extends DrawComponent {
@Override
void draw() {
System.out.println("画图");
}
}
/**
* Decorator,装饰抽象类,继承了Component
* 从外类来扩展Component类的功能,但对于Component来说,
* 是无需知道Decorator的存在的
*/
static abstract class DrawDecorator extends DrawComponent {
protected DrawComponent component;
public DrawComponent getComponent() {
return component;
}
public void setComponent(final DrawComponent component) {
this.component = component;
}
@Override
void draw() {
if (component != null) {
component.draw();
}
}
}
static class ShapeDrawDecorator extends DrawDecorator {
@Override
void draw() {
super.draw();
System.out.println("设置形状");
}
}
static class ColorDrawDecorator extends DrawDecorator {
@Override
void draw() {
super.draw();
System.out.println("设置颜色");
}
}
public static void main(String[] args) {
DrawApi drawApi = new DrawApi();
ShapeDrawDecorator shapeDrawDecorator = new ShapeDrawDecorator();
shapeDrawDecorator.setComponent(drawApi);
ColorDrawDecorator colorDrawDecorator = new ColorDrawDecorator();
colorDrawDecorator.setComponent(shapeDrawDecorator);
colorDrawDecorator.draw();
}
8.适配器模式
概念:
将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。(比如Android中列表的Adapter)
分为类适配器,对象适配器
IDrawApi为绘图接口,Printer为不兼容IDrawApi的打印器功能类,Adapter通过继承或者组合兼容两种功能
问题:
应该选择类适配器还是对象适配器?
如果Printer接口并不多,那两种实现方式都可以。
如果Printer接口很多,而且Printer和IDrawApi接口定义大部分都相同,那我们推荐使用类适配器,因为Adaptor复用父类Printer的接口,比起对象适配器的实现方式,Adaptor的代码量要少一些。
如果Printer接口很多,而且Printer和IDrawApi接口定义大部分都不相同,那我们推荐使用对象适配器,因为组合结构相对于继承更加灵活。
示例:
类适配器:基于继承实现
/**
* 类适配器,基于继承
*/
interface IDrawApi {
void draw();
}
/**
* 打印
*/
static class Printer {
public void print() {
System.out.println("打印");
}
}
/**
* Adapter既可以绘画也可以打印
*/
static class Adapter extends Printer implements IDrawApi {
@Override
public void draw() {
System.out.println("绘画");
}
@Override
public void print() {
super.print();
}
}
public static void main(String[] args) {
Adapter adapter = new Adapter();
adapter.draw();
adapter.print();
}
示例:
对象适配器:基于组合实现
/**
* 绘图
*/
interface IDrawApi {
void draw();
}
/**
* 打印
*/
static class Printer {
public void print() {
System.out.println("打印");
}
}
/**
* Adapter既可以绘画也可以打印
*/
static class Adapter implements IDrawApi {
private Printer mPrinter;
public Adapter(final Printer printer) {
mPrinter = printer;
}
@Override
public void draw() {
System.out.println("绘画");
}
public void print() {
mPrinter.print();
}
}
public static void main(String[] args) {
Adapter adapter = new Adapter(new Printer());
adapter.draw();
adapter.print();
}
9.门面模式
概念:
也叫外观模式,为子系统提供一组统一的接口,定义一组高层接口让子系统更易用。
简单来说,就是将多个接口调用替换为一个门面接口调用。
static class TextHelper {
public void print() {
System.out.println("写字");
}
}
static class DrawHelper {
public void print() {
System.out.println("画图");
}
}
static class PrinterManager {
TextHelper mTextHelper;
DrawHelper mDrawHelper;
public PrinterManager(final TextHelper textHelper, final DrawHelper drawHelper) {
mTextHelper = textHelper;
mDrawHelper = drawHelper;
}
public void print() {
if (mTextHelper != null) {
mTextHelper.print();
}
if (mDrawHelper != null) {
mDrawHelper.print();
}
}
}
public static void main(String[] args) {
PrinterManager printerManager = new PrinterManager(new TextHelper(), new DrawHelper());
printerManager.print();
}
10.组合模式
概念:
将一组对象组织(Compose)成树形结构,以表示一种“部分-整体”的层次结构。
组合让客户端(在很多设计模式书籍中,“客户端”代指代码的使用者。)可以统一单个对象和组合对象的处理逻辑。
示例:
希望在内存中构建整个公司的人员架构图(部门、子部门、员工的隶属关系),
并且提供接口计算出部门的薪资成本(隶属于这个部门的所有员工的薪资和)。
采用组合的方式可以更方便的处理员工与部门的工资计算。
//人力资源
static abstract class HumanResource {
protected long id;
protected double salary;
public HumanResource(long id) {
this.id = id;
}
public long getId() {
return id;
}
public abstract double calculateSalary();
}
//员工
static class Employee extends HumanResource {
public Employee(long id, double salary) {
super(id);
this.salary = salary;
}
@Override
public double calculateSalary() {
return salary;
}
}
//部门
public static class Department extends HumanResource {
private List<HumanResource> subNodes = new ArrayList<>();
public Department(long id) {
super(id);
}
@Override
public double calculateSalary() {
double totalSalary = 0;
for (HumanResource hr : subNodes) {
totalSalary += hr.calculateSalary();
}
this.salary = totalSalary;
return totalSalary;
}
public void addSubNode(HumanResource hr) {
subNodes.add(hr);
}
}
public static void main(String[] args) {
//it部门
Department itDepartment = new Department(1);
itDepartment.addSubNode(new Employee(1, 5000));
itDepartment.addSubNode(new Employee(2, 5100));
itDepartment.addSubNode(new Employee(3, 5200));
itDepartment.addSubNode(new Employee(4, 5300));
itDepartment.addSubNode(new Employee(5, 5400));
//客户部门
Department customerDepartment = new Department(1);
customerDepartment.addSubNode(new Employee(1, 1000));
customerDepartment.addSubNode(new Employee(2, 1100));
customerDepartment.addSubNode(new Employee(3, 1200));
customerDepartment.addSubNode(new Employee(4, 1300));
customerDepartment.addSubNode(new Employee(5, 1400));
//总薪水
double salaryAll = itDepartment.calculateSalary() + customerDepartment.calculateSalary();
System.out.println("总薪水:" + salaryAll);
}
11.享元模式
概念:
当一个系统中存在大量重复对象的时候,如果这些重复的对象是不可变对象,
我们就可以利用享元模式将对象设计成享元,在内存中只保留一份实例,供多处代码引用
示例:
直播间需要实现弹幕表情功能,每一个弹幕的id和表情是固定的,但是x,y坐标不同。
// 享元类,弹幕
static class BarrageUnit {
public int id;
private String face;
public BarrageUnit(final int id, final String face) {
this.id = id;
this.face = face;
}
@Override
public String toString() {
return "BarrageUnit{" +
"id=" + id +
", face='" + face + '\'' +
'}';
}
}
//弹幕类
static class Barrage {
public BarrageUnit mBarrageUnit;
public float x, y;
public Barrage(final BarrageUnit barrageUnit, final float x, final float y) {
mBarrageUnit = barrageUnit;
this.x = x;
this.y = y;
}
@Override
public String toString() {
return "Barrage{" +
"mBarrageUnit=" + mBarrageUnit +
", x=" + x +
", y=" + y +
'}';
}
}
static class ChessPieceUnitFactory {
private static final Map<Integer, BarrageUnit> barrages = new HashMap<>();
static {
barrages.put(1, new BarrageUnit(1, "微笑"));
barrages.put(2, new BarrageUnit(2, "愤怒"));
barrages.put(3, new BarrageUnit(3, "狗头"));
//...省略初始化其他弹幕的代码...
}
public static BarrageUnit getChessPiece(int barrageId) {
return barrages.get(barrageId);
}
}
static class LiveRoom {
private final Map<Integer, Barrage> barrages = new HashMap<>();
//展示表情弹幕
public void showFace(int barrageId, int toPositionX, int toPositionY) {
barrages.put(barrageId, new Barrage(
ChessPieceUnitFactory.getChessPiece(barrageId), toPositionX, toPositionX));
}
public void draw() {
System.out.println("绘制弹幕:" + barrages.toString());
}
}
public static void main(String[] args) {
LiveRoom liveRoom = new LiveRoom();
liveRoom.showFace(1,100,100);
liveRoom.showFace(2,200,200);
liveRoom.showFace(3,300,300);
liveRoom.draw();
}