年轻人不讲武德 之 通过设计模式解读设计原则

前言

  现在越来越多年轻人不讲武德,明明是java面向对象程序员,但是天天写着面向过程的代码。你和他们谈编程范式、设计原则,设计模式,他们会说不就一个简单的if/else吗,用什么设计模式,搞那么复杂!不是闲的,是你的认知水平不够。设计模式站在软件设计层面,不仅为了解决眼前业务,更多的是软件复用性、可读性、扩展性、可靠性、高内聚、低耦合。先来了解一下常用的设计原则有哪些?

设计原则

  1. 开闭原则: 即对扩展进行开放,对修改进行关闭。具象点解释,比如新来一个需求,通过扩展提供方代码的方式,而不是修改原来调用处的代码。
  2. 单一职责原则:即一个类只处理一种或一类业务场景需求,高内聚。
  3. 接口隔离原则:即客户端不应该依赖它不需要的接口,接口设计时按最新粒度拆分,比如mybatis-plus中的BaseMap违背此原则。
  4. 依赖倒置原则:即面向抽象类和接口编程。
  5. 迪米特法则:即最少知道原则,具象点解释,比如我们只调用直接依赖类,不调用间接依赖类,不去过多关注依赖类的实现逻辑,做到最少知道。
  6. 里式替换原则:即父类出现的地方,子类都可以替代。具象点解释,我们的子类尽量少的重写父类的方法,或者说重写时要保证不改变父类逻辑语义,即程序由子类运行时不报逻辑错误。
  7. 合成复用原则:即多用合成和聚合方式,替代继承关系,降低类之间的耦合度。

下面我们先通过一个简单案例来解读设计模式背后常用的设计原则。

案例如下:

业务描述:

  现有一个资讯类APP,资讯分多个频道,需统计每个频道(如:篮球频道、足球频道、WWE频道)下,不同资讯类型(如:图文资讯、图集资讯、视频资讯)的点赞数、阅读数、收藏数。

案例分析:

  设计需满足后续可以动态扩展资讯频道和资讯类型且改动尽量少,如再添加一个资讯频道叫“视频频道”,则设计需满足可以计算“视频频道“的图文、图集、视频资讯的点赞/阅读/收藏数,或者再添加一种资讯类型叫“娱乐资讯”,对应每个资讯频道也需统计该类资讯的点赞/阅读/收藏数。

解决方案:

  资讯频道和资讯类型都有可能变化,那么让资讯频道和资讯类型充分解耦,让其各自可以独立变化,最后通过聚合的方式使二者产生调用关系。此处场景适合桥接模式,让抽象和具体实现进行分离,各自独立变化。此案例中,不同的资讯频道就是我们的抽象,不同的资讯类型就是我们对点赞/阅读/收藏数的不同具体实现。

类图分析:

年轻人不讲武德 之 通过设计模式解读设计原则_第1张图片

实例代码:

public interface INewsStatistics {

    NewsTypeEnum type();

    Integer readCount(Long channelId);

    Integer likeCount(Long channelId);

    Integer collectCount(Long channelId);

}

先定义资讯统计接口 INewsStatistics

@Service
@RequiredArgsConstructor
public class ImageNewsStatistics implements INewsStatistics {

    private final NewsMapper newsMapper;

    @Override
    public NewsTypeEnum type() {
        return NewsTypeEnum.IMAGE;
    }

    @Override
    public Integer readCount(Long channelId) {
        return newsMapper.readCount(channelId);
    }

    @Override
    public Integer likeCount(Long channelId) {
        return newsMapper.likeCount(channelId);
    }

    @Override
    public Integer collectCount(Long channelId) {
        return newsMapper.collectCount(channelId);
    }

}

定义图集资讯统计实现类

@Service
@RequiredArgsConstructor
public class InfoNewsStatistics implements INewsStatistics {

    private final NewsMapper newsMapper;

    @Override
    public NewsTypeEnum type() {
        return NewsTypeEnum.INFO;
    }

    @Override
    public Integer readCount(Long channelId) {
        return newsMapper.readCount(channelId);
    }

    @Override
    public Integer likeCount(Long channelId) {
        return newsMapper.likeCount(channelId);
    }

    @Override
    public Integer collectCount(Long channelId) {
        return newsMapper.collectCount(channelId);
    }

}

定义图文资讯统计实现类

@Service
@RequiredArgsConstructor
public class VideoNewsStatistics implements INewsStatistics{

    private final NewsMapper newsMapper;

    @Override
    public NewsTypeEnum type() {
        return NewsTypeEnum.VIDEO;
    }

    @Override
    public Integer readCount(Long channelId) {
        return newsMapper.readCount(channelId);
    }

    @Override
    public Integer likeCount(Long channelId) {
        return newsMapper.likeCount(channelId);
    }

    @Override
    public Integer collectCount(Long channelId) {
        return newsMapper.collectCount(channelId);
    }

}

定义视频资讯统计实现类

public abstract class Channel {

    private List iNewsStatistics;

    public Channel(List iNewsStatistics) {
        this.iNewsStatistics = iNewsStatistics;
    }

    protected Map readCount(Long channelId) {
        return iNewsStatistics.stream()
                .collect(Collectors.toMap(statistics -> statistics.type(), statistics -> statistics.readCount(channelId)));
    }

    protected Map likeCount(Long channelId) {
        return iNewsStatistics.stream()
                .collect(Collectors.toMap(statistics -> statistics.type(), statistics -> statistics.likeCount(channelId)));
    }

    protected Map collectCount(Long channelId) {
        return iNewsStatistics.stream()
                .collect(Collectors.toMap(statistics -> statistics.type(), statistics -> statistics.collectCount(channelId)));
    }

}

定义频道Channel 抽象类,并聚合iNewsStatistics资讯统计接口

@Service
public class BasketballChannel extends Channel {

    private List iNewsStatistics;

    public BasketballChannel(List iNewsStatistics) {
        super(iNewsStatistics);
    }

    @Override
    public Map readCount(Long channelId) {
        return super.readCount(channelId);
    }

    @Override
    public Map likeCount(Long channelId) {
        return super.likeCount(channelId);
    }

    @Override
    public Map collectCount(Long channelId) {
        return super.collectCount(channelId);
    }

}

定义频道Channel 抽象类的具体实现类BasketballChannel 篮球频道,对应的其他FootballChannel足球频道、WWEChannel频道类似,此处省略。使用时只需前端传入对应频道ID,调用对应频道的readCount、likeCount、collectCount方法,即可查询该频道下不同资讯类型的阅读/点赞/收藏数。

设计原则解读:

  1. 开闭原则:资讯频道和资讯类型的新增,只需添加对应Channel抽象类的子类和INewsStatistics接口的实现类,而调用方代码无需改动,满足开闭原则。
  2. 依赖倒置:代码编写都是面向抽象类Channel和INewsStatistics接口,未面向具体实现,符合依赖倒置原则。
  3. 里式替换:虽然资讯频道类重写了父类Channel的方法,但未改变父类方法的逻辑语义,即父类出现的地方,可以让子类完全替换,所以也符合里式替换原则。
  4. 单一职责:将不同的资讯频道和资讯类型拆开,每一个资讯频道类和资讯类型类只负责各自领域的业务,符合单一职责原则。
  5. 迪米特法则:Channel类直接依赖INewsStatistics资讯统计类,在Channel类计算点赞/阅读/收藏数时,直接调用INewsStatistics资讯统计类的方法,而没有过度关注计算方法的细节,也未调用其他INewsStatistics类中的间接对象,做到最少知道原则,所以也符合迪米特法则。
  6. 合成复用原则:此处除了常说的6大设计原则之外,此案例中采用Channel类聚合INewsStatistics接口方式,替代相应的继承方式,降低类之间的耦合度,也符合合成复用原则。

你可能感兴趣的:(设计模式,java,设计模式,编程语言)