把书读薄 | 《设计模式之美》学习导读 & 面向对象(中)

简介: 设计模式某些概念比较抽象,认真看完有时似懂非懂,往往没过多久就忘了,在实际设计与编码中,也不知道如何下手,所以需要落地,想办法加深理解,阅读开源项目,应用到项目中等等。 本文是 学习导读(3讲)和面向对象(11讲) 的浓缩总结,二手知识加工难免有所纰漏,感兴趣有时间的可自行查阅原文,谢谢。

⑤ 多态 (Polymorphism)


子类可以替换父类,一个多态的简单例子:


public class IBrother {
    void doSomething() {
        System.out.println("搞事");
    }
}
public class TeaBrother extends IBrother {
    @Override
    void doSomething() {
        System.out.println("给大佬递茶");
    }
}
public class Test {
    public static void main(String[] args) {
        IBrother brother = new TeaBrother();
        brother.doSomething();  // 输出结果:给大佬递茶
    }
}


从上面这个例子,可以看到多态这种需要编程语言支持下述三个语法机制:


父类可以引用子类对象 → 支持继承 → 子类可以重写(Override)父类中的方法。


上述代码是Java中的 运行时多态,还有一个编译时多态,通过 方法重载(Overload) 实现。


多态还有两种较常见的实现方式:接口类语法(注入) 和 duck-typing语法(Python中类具有相同的方法即可实现多态)。


多态的意义


提高代码的扩展性和复用性,很多设计原则、设计模式、编程技巧的代码实现基础。


⑥ 接口与抽象类的区别


  • 抽象类(is-a):不允许被实例化,只能被继承,可以包含属性和方法,子类继承需实现所有抽象方法;
  • 接口(has-a):不能包含属性(成员变量),只能声明方法,不能含代码实现,实现接口时需实现声明的所有方法;


使用抽象类更多是为了 代码复用,强制子类实现所有抽象方法,减少类误用导致报错。


而接口侧重于 解耦,对行为的一种抽象(协议/契约),调用者只需关注抽象接口,无需了解具体实现,约定与实现分离,降低代码间的耦合,提高代码的扩展性。


⑦ 为何基于接口而非实现编程


接口和实现分离,封装不稳定的实现,暴露稳定的接口,上游系统面向接口而非面向实现编程,不依赖不稳定的实现细节,当实现发生改变时,上游代码不许改动,降低耦合,提高扩展性。


如何将原则应用到实践中


  • 函数命名不暴露任何实现细节 (如:uploadToQiniuYun(×) → upload(√) )
  • 封装具体的实现细节 (如:上传流程不该暴露给调用者,应在类内部分封装)
  • 为实现类定义抽象的接口 (依赖统一的接口定义,使用者依赖接口,而不是具体实现类来编程)


做软件开发时,一定要有抽象、封装和接口意识,接口的定义只表明做什么,而不是怎么做。设计接口时多思考这样的接口设计是否足够通用,是否能够做到在替换具体接口实现时,不需要任何接口定义的改动。


是否需要为每个类都定义接口


凡事都讲究一个 ,滥用这条原则非得给每个类都定义接口,接口满天飞会导致不必要的开发负担。回归设计初衷,如果在业务场景中,某个功能只有一种实现方式,未来也不可能被其他方式替代,就没必要为其设置接口,直接用实现类就好了。


⑧ 为何说要多用组合少用继承


继承层次过审、过复杂,会影响到代码的可读性和可维护性,看一个子类跳一堆父类,父类修改影响所有子类逻辑。


举个例子,设计一个奶茶店茶的类,先定义一个抽象类 AbstractTea,然后是各种奶茶,珍珠奶茶,椰果奶茶,波霸奶茶等


AbstractTea → 珍珠奶茶
            → 椰果奶茶
            → 波霸奶茶


但,有些店除了卖奶茶还卖纯茶,为了区分需要在AbstractTea的基础上派生出两个更加细分的抽象类:


AbstractTea → AbstractMilkTea → 珍珠奶茶
                              → 椰果奶茶
                              → 波霸奶茶
            → AbstractPureTea → 纯四季春
                              → 纯绿妍
                              → 纯金凤茶王


继承关系从两层变成三层,层次还算浅,如果再加一个条件呢,冷热:


AbstractTea → AbstractMilkTea → AbstractMilkColdTea → 冰珍珠奶茶
                                                    → 冰椰果奶茶
                                                    → 冰波霸奶茶
                              → AbstractMilkHotTea  → 热珍珠奶茶
                                                    → 热椰果奶茶
                                                    → 热波霸奶茶
            → AbstractPureTea → AbstractPureColdTea → 冰纯四季春
                                                    → 冰纯绿妍
                                                    → 冰纯金凤茶王
                              → AbstractPureHotTea  → 热纯四季春
                                                    → 热纯绿妍
                                                    → 热纯金凤茶王


如果再加一个条件,是否加糖,2333,五层直接原地裂开。


上述的问题其实可以通过 组合(Composition)、接口和委托(Delegation) 这三种技术手段来解决。


public interface IProduct {
    void product();
}
public interface ITemperature {
    void temperature();
}
public interface ISweet {
    void sweet();
}
// 实现上述接口
public class ColdSugarPearlMilkTea implements IProduct, ITemperature, ISweet {
    @Override
    public void product() {
        System.out.println("原料是:牛奶+茶");
    }
    @Override
    public void sweet() {
        System.out.println("加糖");
    }
    @Override
    public void temperature() {
        System.out.println("冷");
    }
}


接口只声明方法,不定义实现,每种茶都要实现接口中的方法,有些实现逻辑是一样的,代码重复,引入组合委托来消除此问题:


public class MilkTea implements IProduct {
    @Override
    public void product() {
        System.out.println("原料是:牛奶+茶");
    }
}
public class Sugar implements ISweet{
    @Override
    public void sweet() {
        System.out.println("加糖");
    }
}
public class Cold implements ITemperature{
    @Override
    public void temperature() {
        System.out.println("冷");
    }
}
public class ColdSugarPearlMilkTea implements IProduct, ITemperature, ISweet {
    // 组合
    private MilkTea milkTea = new MilkTea();
    private Cold cold = new Cold();
    private Sugar sugar = new Sugar();
    @Override
    public void product() {
        milkTea.product();  // 委托
    }
    @Override
    public void sweet() {
        sugar.sweet();
    }
    @Override
    public void temperature() {
        cold.temperature();
    }
}


实际开发中要根据具体情况选择继承还是组合,类间继承结构稳定、层次较浅,关系不复杂,可以大胆地使用继承。反制,就尽量使用组合替代继承。除此之外一些设计模式、特殊应用场景,会固定使用继承或组合。


⑨ 贫血模型和充血模型


MVC → Model(数据层)、View(展示层)、Controller(逻辑层),很多项目并不会100%遵从这种固定的分层方式。


现在很多Web或App项目都是前后端分离的,一般将后端项目分为下面几层:


  • Repository层 → 负责数据访问;
  • Service层 → 负责业务逻辑;
  • Controller层 → 负责暴露接口;


一个充血模型的示例如下:


////////// Controller+VO(View Object) //////////
public class UserController {
  private UserService userService; //通过构造函数或者IOC框架注入
  public UserVo getUserById(Long userId) {
    UserBo userBo = userService.getUserById(userId);
    UserVo userVo = [...convert userBo to userVo...];
    return userVo;
  }
}
public class UserVo {//省略其他属性、get/set/construct方法
  private Long id;
  private String name;
  private String cellphone;
}
////////// Service+BO(Business Object) //////////
public class UserService {
  private UserRepository userRepository; //通过构造函数或者IOC框架注入
  public UserBo getUserById(Long userId) {
    UserEntity userEntity = userRepository.getUserById(userId);
    UserBo userBo = [...convert userEntity to userBo...];
    return userBo;
  }
}
public class UserBo {//省略其他属性、get/set/construct方法
  private Long id;
  private String name;
  private String cellphone;
}
////////// Repository+Entity //////////
public class UserRepository {
  public UserEntity getUserById(Long userId) { //... }
}
public class UserEntity {//省略其他属性、get/set/construct方法
  private Long id;
  private String name;
  private String cellphone;
}


像UserBo这样,只包含数据,不包含业务逻辑的类,就叫做贫血模型 (Anemic Domain Model),同理UserVo 和UserEntity也是贫血模型。而 充血模型 (Rich Domain Model) 正好相反,类中 既包含数据,又包含业务逻辑


贫血模型的Service层包含Service类和BO类(贫血模型),业务逻辑集中在Service类中; 重学模型的Service层包含Service类和Domain类(充血模型,包含数据和业务),Service类较单薄。


Tips:这部分只是看是明白了,先暂时理解成:贫血模型 → 只有属性的类,好处是容易看懂,充血模型 → 有属性也有业务逻辑的类,好处是代码复用,坏处是成本较高。


相关文章
|
5月前
|
设计模式 存储 Java
认真学习设计模式之观察者模式(Observer Pattern)
认真学习设计模式之观察者模式(Observer Pattern)
31 0
|
6月前
|
设计模式 缓存 Java
认真学习设计模式之建造者模式(Builder Pattern)
认真学习设计模式之建造者模式(Builder Pattern)
60 1
|
4月前
|
设计模式 监控 安全
多线程设计模式【多线程上下文设计模式、Guarded Suspension 设计模式、 Latch 设计模式】(二)-全面详解(学习总结---从入门到深化)
多线程设计模式【多线程上下文设计模式、Guarded Suspension 设计模式、 Latch 设计模式】(二)-全面详解(学习总结---从入门到深化)
62 0
|
7天前
|
设计模式 存储 前端开发
Java从入门到精通:2.2.1学习Java Web开发,了解Servlet和JSP技术,掌握MVC设计模式
Java从入门到精通:2.2.1学习Java Web开发,了解Servlet和JSP技术,掌握MVC设计模式
|
5月前
|
设计模式 存储 Java
认真学习设计模式之命令模式(Command Pattern)
认真学习设计模式之命令模式(Command Pattern)
81 0
|
4月前
|
设计模式 Java 编译器
Java 设计模式最佳实践:一、从面向对象到函数式编程
Java 设计模式最佳实践:一、从面向对象到函数式编程
62 0
|
4月前
|
设计模式 安全 Java
多线程设计模式【线程安全、 Future 设计模式、Master-Worker 设计模式 】(一)-全面详解(学习总结---从入门到深化)
多线程设计模式【线程安全、 Future 设计模式、Master-Worker 设计模式 】(一)-全面详解(学习总结---从入门到深化)
28 0
|
5月前
|
设计模式 Java 关系型数据库
认真学习设计模式之适配器模式(Adapter Pattern)/包装器模式
认真学习设计模式之适配器模式(Adapter Pattern)/包装器模式
62 0
|
5月前
|
设计模式 Java 数据库连接
认真学习设计模式之外观模式(Facade Pattern)
认真学习设计模式之外观模式(Facade Pattern)
28 0
|
5月前
|
设计模式 Java 应用服务中间件
认真学习设计模式之职责链模式((Chain of Responsibility Pattern)
认真学习设计模式之职责链模式((Chain of Responsibility Pattern)
63 0

相关实验场景

更多