设计原则:不要为了复用而使用继承
背景
今天上午和以为朋友聊了一个设计问题:如何消除仓库相关的单据的Repository中的重复逻辑?如:入库单Repository和出库单Repository之间的重复。可以有很多方式消除重复,在不同级别消除重复,如:继承、组合、掺入、帮助类、帮助方法。本文只说出我的观点:不要为了复用而使用继承。
为什么要得出这个结论:在单实现继承模型下,你复用了一个基类的实现,就不能复用其它基类的实现了,接口继承 + 扩展类型(Mixin)可以很好的解决这个问题。
设计的演化
下面我会演示:待重构的重复代码-》用继承消除重复-》用扩展类(Mixin)消除重复-》Ruby的鸭子类型 + Mixin的实现(元编程可以更牛叉,有机会再说)。
待重构的代码
注意:出库单仓储和入库单仓储的“根据编号获取单据”重复了。
类图
代码
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.Linq.Expressions; 7 8 namespace CSharpStudy.MixinStudy.V1 9 { 10 class Aggregate { } 11 12 interface IRepository<T> where T : Aggregate 13 { 14 IEnumerable<T> Where(Expression<Func<T, bool>> condition); 15 } 16 17 class Repository<T> : IRepository<T> 18 where T : Aggregate 19 { 20 public IEnumerable<T> Where(Expression<Func<T, bool>> condition) 21 { 22 throw new NotImplementedException(); 23 } 24 } 25 26 class 入库单 : Aggregate 27 { 28 public string 单据编号 { get; set; } 29 } 30 31 class 出库单 : Aggregate 32 {
用继承消除重复
当我看到上面的重复代码的时候,第一印象是引入两个基类:仓库单据基类和仓库单据仓储基类。
类图
代码
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.Linq.Expressions; 7 8 namespace CSharpStudy.MixinStudy.V2 9 { 10 class Aggregate { } 11 12 interface IRepository<T> where T : Aggregate 13 { 14 IEnumerable<T> Where(Expression<Func<T, bool>> condition); 15 } 16 17 class Repository<T> : IRepository<T> 18 where T : Aggregate 19 { 20 public IEnumerable<T> Where(Expression<Func<T, bool>> condition) 21 { 22 throw new NotImplementedException(); 23 } 24 } 25 26 class 仓库单据基类 : Aggregate 27 { 28 public string 单据编号 { get; set; } 29 } 30 31 class 入库单 : 仓库单据基类 { } 32 33 class 出库单 : 仓库单据基类 { } 34 35 class 仓库单据仓储基类<T> : Repository<T> 36 where T : 仓库单据基类 37 { 38 public IEnumerable<T> 根据编号获取单据(string 单据编号) 39 { 40 return this.Where(x => x.单据编号 == 单据编号); 41 } 42 } 43 44 class 入库单仓储 : 仓库单据仓储基类<入库单> { } 45 46 class 出库单仓储 : 仓库单据仓储基类<出库单> { } 47 }
用扩展类(Mixin)消除重复
我对了吗?没有多态,只是为了复用就引入继承,是否合理呢?
类图
代码
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.Linq.Expressions; 7 8 namespace CSharpStudy.MixinStudy.V3 9 { 10 class Aggregate { } 11 12 interface IRepository<T> where T : Aggregate 13 { 14 IEnumerable<T> Where(Expression<Func<T, bool>> condition); 15 } 16 17 class Repository<T> : IRepository<T> 18 where T : Aggregate 19 { 20 public IEnumerable<T> Where(Expression<Func<T, bool>> condition) 21 { 22 throw new NotImplementedException(); 23 } 24 } 25 26 class 仓库单据基类 : Aggregate 27 { 28 public string 单据编号 { get; set; } 29 } 30 31 class 入库单 : 仓库单据基类 { } 32 33 class 出库单 : 仓库单据基类 { } 34 35 static class 仓库单据基类仓储扩展 36 { 37 public static IEnumerable<T> 根据编号获取单据<T>(this IRepository<T> that, string 单据编号) 38 where T : 仓库单据基类 39 { 40 return that.Where(x => x.单据编号 == 单据编号); 41 } 42 } 43 }
Ruby的鸭子类型 + Mixin的实现
代码
1 # coding: utf-8 2 3 class Aggregate 4 end 5 6 class Repository 7 def Where(condition) 8 end 9 end 10 11 class C仓库单据基类 < Repository 12 attr_accessor :单据编号 13 end 14 15 class C入库单 < C仓库单据基类 16 end 17 18 class C出库单 < C仓库单据基类 19 end 20 21 module C仓库单据基类仓储扩展 22 def 根据编号获取单据(单据编号) 23 return self.Where({:单据编号 => 单据编号}) 24 end 25 end 26 27 class C入库单仓储 < Repository 28 include C仓库单据基类仓储扩展 29 end 30 31 class C出库单仓储 < Repository 32 include C仓库单据基类仓储扩展 33 end
Ruby正统的支持了Mixin,鸭子类型天生具备泛型的特点,比泛型强大,元编程更是牛叉(本文没有体现)。
备注
做一件事如果只有一个选择,就说明有问题了,多思考几个方案,折中后考虑一个方案。
设计原则:不要为了复用而使用继承相关推荐
- 七大设计原则之合成复用原则
一.合成复用原则介绍 合成复用原则是指尽量使用合成/聚合的方式,而不是使用继承.类图介绍如下: 二.依赖关系.组合关系.聚合关系介绍 2.1依赖关系(Dependency) 只要是在类中用到了对方,那 ...
- 面向对象设计原则之合成复用原则
合成复用原则又称为组合/聚合复用原则(Composition/Aggregate Reuse Principle, CARP),其定义如下: 合成复用原则(Composite Reuse Princi ...
- 设计原则之合成复用原则
合成复用原则是指: 尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现. 通常类的复用分为继承复用和合成复用两种. 继承复用虽然有简单和易实现的优点,但它也存在以下缺点: 1. 继承 ...
- 设计模式之美总结(设计原则篇)
title: 设计模式之美总结(设计原则篇) date: 2022-10-27 17:31:42 tags: 设计模式 categories: 技术书籍及课程 cover: https://cover ...
- 设计原则:里式替换原则(LSP)
系列文章 设计原则:单一职责(SRP) 设计原则:开闭原则(OCP) 设计原则:里式替换原则(LSP) 设计原则:接口隔离原则(ISP) 设计原则:依赖倒置原则(DIP) 何谓高质量代码? 理解RES ...
- 设计原则与思想:设计原则12讲
文章目录 设计原则与思想:设计原则(12讲) 理论一:对于单一职责原则,如何判定某个类的职责是否够"单一"? 如何理解单一职责原则(SRP)? 如何判断类的职责是否足够单一? 类的 ...
- 设计模式-02.经典设计原则-第一节-单一职责原则,开闭原则,里式替换,接口隔离【万字长文系列】
文章目录 设计模式经典设计原则-第一节 单一职责原则(SRP) 如何理解单一职责原则? 如何判断类的职责是否足够单一? 类的职责是否设计得越单一越好? 开闭原则(OCP) 如何理解"对扩展开 ...
- Go设计模式(3)-设计原则
上一篇文章Go设计模式(2)-面向对象分析与设计里讲过,做设计最重要的是保留合适的扩展点.如何才能设计出合适的扩展点呢? 这篇文章会讲解一下经典的设计原则.这些设计原则大家可能都听过,但可能没有想过为 ...
- 004.设计原则与思想:设计原则
设计原则 一.理论一:对于单一职责原则,如何判定某个类的职责是否够"单一"? 1. 如何理解单一职责原则(SRP)? 二. 如何做到"对扩展开放.修改关闭"?扩 ...
- 设计模式之软件设计原则篇
3.软件设计原则 本文的内容绝大部分借鉴了https://www.jianshu.com/u/cc272db15285的内容,感兴趣的小伙伴可以进入其简书浏览更细的内容,讲的非常好. 在软件开发中,为 ...
最新文章
- IEEE发布人工智能伦理标准,确保人类不受影响
- 鸡年除夕全天微信红包收发量达142亿个增长75.7%
- CentOS VS Ubuntu,谁才是更好的 Linux 版本?
- Microsoft SQL Server 2005 查询分页
- SpringBoot与jackson.databind兼容报错问题
- 20多年的朋友如兄弟,没有矛盾4个人如今都绝交了什么原因?
- Hyperledger Fabric 节点类型Commiter、Endorser、Leader、Anchor
- Javashop 7.0 增加小程序支付(二次开发)
- 关于Chrome浏览器书签被更改的分析
- 你必须牢记的Win7快捷键
- word中,解决插入形状后固定位置不变。
- TearDrop拒绝服务攻击
- grafana 获取禅道bug执行sql语句的问题
- 微信公众号无法获得用户列表中的昵称和用户头像URL
- JKD+Tomcat+Eclipse基础配置
- 关于出生日期、邮箱、上传下载操作的元素点分析
- 【安装系统】windows10、ubuntu16.04系统安装
- gcc -I -i -L -l 参数区别 / -l(静态库/动态库)
- Ubuntu 18.04 连接有线网络
- 解决JAVA键盘监听的延迟现象以及八个方向的运动
热门文章
- LeetCode15:三数之和(转载思路)
- python日志,支持彩色打印和文件大小切片写入和写入mongodb
- rabbitmq延迟队列相关
- 【一周读书】哲学家,你们都干了些什么?
- 需求阶段如何书写Use Case
- html5怎么圆圈怎么打开,HTML5动感圆圈
- vrp 节约算法 c++_数据结构和算法(Golang实现)(8.1)基础知识-前言
- 项目配置不当引发了数据泄露,人已裂开!!
- 爬取了BOSS直聘、拉勾等近1000+招聘需求,总结出3年+Java开发的高频技术需求
- Java的transient关键字