五,SOLID
SOLID 原则,实际上,SOLID 原则并非单纯的 1 个原则,而是由 5 个设计原则组成的,它们分别是:单一职责原则、开闭原则、里式替换原则、接口隔离原则和依赖反转原则,依次对应 SOLID 中的 S、O、L、I、D 这 5 个英文字母。
SRP:单一职责原则
一个软件系统的最佳结构高度依赖开发这个系统的组织的内部结构。这样每一个软件只有一个需要被修改的理由 - 基于康威定律的一个推论。
历史上的描述:任何一个软件模块都应该有且仅有一个被修改的原因。
现实上的描述:任何一个软件模块都应该只对一个用户或系统利益相关者负责。
最终描述:任何一个软件模块都应该只对一类行为者(对系统变更相似的用户或系统利益相关方)负责。
OCP:开闭原则
如果软件系统想要更容易的被修改,那么其设计就必须运行新增代码来修改系统行为,而非只能修改原来代码。
组件依赖方向的控制。信息隐藏。
OCP是我们进行系统架构设计的主导原则,其主要目的是让系统易于扩展,同时限定其每次改动所影响的范围。实现方式是通过将系统划分为一系列的组件,并将这些组件间的依赖关系按照层次结构进行组织,使得高阶组件不会因为低价组件被修改而受到影响。
LSP:里氏替换原则
一个著名的子类定义。如果想用可以替换的组件来构建软件系统,那么这些组件必须遵守同一个约定,以便这些组件之间相互替换。
继承的使用指导,指导接口与其实现方式的设计原则。
ISP:接口隔离原则
在设计中避免不必要的依赖。任何层次的软件设计如果依赖不需要的东西,都是有害的。
DIP:依赖反转原则
软件架构描述的对象是直接构成系统的抽象组件。该原则指出高层策略性的代码不应该依赖底层的细节实现代码。恰恰相反,那些底层细节的代码实现应该依赖高层的策略性的代码。
如果想要设计一个灵活的系统,在源码层次的依赖关系中就应该多应用抽象类,而非具体实现。抽象层是稳定的。
控制流跨越架构边界的方向与源代码依赖关系跨越边界的方向正好相反,源代码依赖方向永远与控制流方向相反。
案例
通过一个案例来运用一下,具体需求如下:公司内部有一个保险金融类的项目,业务要求给在现有的业务上添加保单续保功能。功能点包括:
1.针对C端用户提供 “待续保保单”,“已续保报道”及“过期续保保单” 数据列表。
2.需要满足C端用户的续保,查询保单基本信息和续保保单基本信息功能。
3.后台支持平台需要按照售卖产品进行续保相关参数配置(如:续保日期,续保目标产品)。
一. 首先案例分析,找出利益相关方。
投保模块:负责用户投保流程并与保险公司对接。
订单模块:负责保单相关交易记录管理。
产品模块:负责产品的基础信息配置,扩展续保功能配置相关功能。
定时任务模块:新增定时任务,匹配续保产品与订单,并将满足续保条件产品订单记录生成新的续保记录。
续保模块:负责为使用方提供续保记录查询数据接口。
将利益相关方通过一张结构图表示出来如下:
二. 确定出利益相关方后,如果功能职责比较单一可直接将模块转换为服务直接进行开发。如新增续保模块,对外只提供续保数据查询接口,可独立为一个服务。也可将其聚合在与其关系紧密的订单模块中,作为子模块存在。
针对案例需求中的3个问题可以做如下划分:
1.针对C端用户提供 “待续保保单”,“已续保报道”及“过期续保保单” 数据列表。由续保模块完成。
2.需要满足C端用户的续保,查询保单基本信息和续保保单基本信息功能。投保模块扩展完成续保功能。查询保单基本信息和续保保单基本信息功能可由订单模块完成。
3.后台支持平台需要按照售卖产品进行续保相关参数配置(如:续保日期,续保目标产品)。由产品模块完成。定时任务模块则负责完成普通订单到续保定的数据流转。
三. 给出案例项目编码中的部分代码
1/** 2 * @author Arvin 3 * @date 2020-12-10 4 * @des 定义一个续保抽象声明 5 */
6public interface ApplyInterface{
7 /** 8 * 9 * @return10 */
11 AjaxResult apply(String param,Integer retry);
12}
1/** 2 * @author Arvin 3 * @date 2020-12-10 4 * @des 中国平安保险公司实现 5 */
6public class PingAnApply implements ApplyInterface{
7 @Override
8 public AjaxResult apply(String param,Integer retry){
9 // TODO your stuff here
10 return null;
11 }
12}
1/** 2 * @author Arvin 3 * @date 2020-12-10 4 * @des 创建动态代理实现 5 */
6public class DynamicProxy{
7
8
9 public static ApplyInterface createInstance(String className) throws Exception{
10
11
12 ClassPool mpool = new ClassPool(true);
13
14
15 //定义类名
16 CtClass mctc = mpool.makeClass(ApplyInterface.class.getName()+"Javassist-BytecodeProxy");
17
18
19 //需要实现的接口
20 mctc.addInterface(mpool.get(InvoiceInterface.class.getName()));
21
22
23 //添加构造函数
24 mctc.addConstructor(CtNewConstructor.defaultConstructor(mctc));
25
26
27 //添加类的字段信息
28 mctc.addField(CtField.make("public " + ApplyInterface.class.getName() +" real;",mctc));
29
30
31 //添加方法,这里使用动态java代码指定内部业务逻辑
32 mctc.addMethod(CtNewMethod.make("public com.ydwf.cloud.framework.util.AjaxResult apply(String param,Integer retry){" +
33 "if (real == null){ " +
34 " real = new "+className+"();" +
35 "} " +
36 "return real.apply(param, retry);"
37 +"}",mctc));
38
39
40
41
42 //基于以上信息生产动态类
43 Class pc = mctc.toClass();
44
45 ApplyInterface proxy = (ApplyInterface)pc.newInstance();
46
47
48 return proxy;
49
50
51 }
52}
1/** 2 * @author Arvin 3 * @date 2020-12-10 4 * @des 创建一个抽象工厂类 5 */
6public class ApplyImplFactory {
7
8
9 private static Map manager = new ConcurrentReaderHashMap();101112 public static ApplyInterface get(String className) throws Exception{
131415 ApplyInterface applyInterface = manager.get(className);161718 if(applyInterface == null){
192021 applyInterface = DynamicProxy.createInstance(className);222324 manager.put(className,applyInterface);252627 }28 return applyInterface;29 }30}
1/** 2 * @author Arvin 3 * @date 2020-12-10 4 * @des 调用续保功能 5 */
6public static void main(String[] args) {
7 String className = "*.PingAnApply"
8 ApplyInterface applyInterface = ApplyImplFactory.get(className);
9 applyInterface.apply(param,retry);
10}
未完待续
编码原则(返场一)
1.应该在代码中多使用抽象接口,尽量避免那些多变的具体实现类。
2.不要在具体实现类上衍生类。
3.不要覆盖(override)保护具体实现的函数。
4.应该避免在代码中写入与任何具体实现相关的名字,或者其他容易变动的事物的名字。
构建组件的3个基本原则(返场二)
1.REP 发布/复用等同原则
软件复用的最小粒度等同其发布的最小粒度。如果想复用某个软件组件的话,一般就必须要求该组件的开发有某种发布流程来驱动,并明确其发布的版本号。
2.CCP 共同闭包原则
我们会将那些会同时修改,并且为了相同目的而修改的类放到同一个组件中,而将不会同时修改,并且不会为了相同目的而修改的类放到不同的组件中。CCP原则认为一个组件不应该存在多个变更原因。
CCP 原则与 OCP 原则也是紧密相连的,CCP 就是 OCP所关注的“闭包”问题。100%的闭包是不可能的,所以我们只能战略性的选择闭包范围。
对于CCP,在此基础上做一些延伸,即将某一类变更所涉及到的类尽量聚合在一起。将变更影响的范围控制在有限组件中。
3.CRP 共同复用原则
不要强迫一个组件用户依赖他们不需要的东西。通常情况下,类很少被单独复用。更常见的情况是多个类同时作为某个可复用的抽象定义被共同复用。CRP原则指导我们将这些类放在同一个组件中,而在这个组件中会存在许多相互依赖的类。更重要的是如果将那些类分开,因为每当一个组件引用了另外一个组件的时候。就等于增加了一条依赖关系。由于这种依赖关系的存在,每当被引用组件发生变更时,引用它的组件一般也要做出相应的调整。
CRP原则与ISP原则告诉我们同样一个原则:不要依赖用不到的东西。REP 和 CCP 原则是粘性原则,他会让组件变的更大。而CRP是排除性原则,他会尽量让组件变得小。