SOLID Pattern是开发clean code的一个基本的理论指导,
它可以帮助开发人员实现具有较高可读性,可维护性以及鲁棒性的代码,
是每一个开发人员应该掌握的知识。
S ingle
Responsibility Principle(单一功能原则)
1 2 A class should have one and only one reason to change, meaning that a class should have only one responsibility.
一个模块(方法,类,组件等)应该只有一个职能, 避免因为多职能间的耦合,
比如状态的耦合,职能实现的耦合等,
导致一个职能发生修改时,其他的职能受到影响。
单一功能原则可能是里面最重要的一个原则,它帮助定义模块的职能边界,
需要注意以下几个方面:
基于现有的业务进行分析,明确模块职能的边界 ,尽量保证一个模块只有一个职能
避免职能的耦合
避免过度设计,基于现有的业务进行设计,
当业务变更时,原本的设计无法满足时,再对设计进行优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class ShoppingCart { private List<Item> items = new LinkedList <>(); public void addItem (Item item) { items.add(item); } public BigDecimal getTotalPrice () { BigDecimal total = BigDecimal.ZERO; for (Item item : items) { total = total.add(item.getPrice()); } return total; } public String print () { String list = items.stream() .map(Item::toString) .collect(Collectors.joining("\n" )); String total = "Total: " + getTotalPrice(); return String.join("\n" , list, total); } }
优化方案:
对职能进行分离,提取到不同的类中
通过组合不同职能的类来实现业务逻辑
O pen-Closed
Principle(开闭原则)
1 Objects or entities should be open for extension but closed for modification.
对相同职能进行扩展时,不应该修改到原有的职能,否则就有可能引入新的bug。
开闭原则主要针对具有相同职能,但不同实现的场景,为了避免因为切换不同实现到引发新的bug。
如何实现开闭原则:
对职能进行分析,确定分类 并提取共有职能 ,抽象出相关的接口
每个种类实现对应的接口
业务逻辑基于接口 来实现职能
当有新的种类时,只需要针对新的种类实现相应的接口即可,而不用不修改原有的业务逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class AreaCalculator { public Long getArea (Object obj) { if (obj instanceof Rectangle) { Rectangle rectangle = (Rectangle) obj; return rectangle.getLength() * rectangle.getWidth(); } if (obj instanceof Square) { Square square = (Square) obj; return square.getLength() * square.getLength(); } throw new RuntimeException ("Invalid Param" ); } }
优化方案:
提取共同的职能---计算面积,抽象出相关的接口
每个形状实现各自的职能接口
业务逻辑基于职能接口进行实现
L iskov
Substitution Principle(里氏替换原则)
1 2 Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it
子类必须遵循父类的行为 和约束 。
尤其是要注意约束,只有完全满足下面这些约束才能进行替换:
入参的约束, 包括作用域,格式,类型等
出参的约束,包括值域,格式,类型等
异常的约束,包括类型,数量等
行为逻辑的约束,指该行为应该要实现怎样的职能
如何实现里氏替换原则:
重新定义父类的职能
将不符合父类的职能的实现从子类中移出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class Rectangle implements Shape { @Override public Double area (Double length, Double width) throws InvalidParameterException, NumberFormatException { if (length < 0 || width < 0 ) { throw new InvalidParameterException (); } checkFormat(length); checkFormat(width); return length * width; } private void checkFormat (Double value) throws NumberFormatException { String s = Double.toString(value); int index = s.indexOf('.' ); int length = s.substring(index + 1 ).length(); if (length > 2 ) { throw new NumberFormatException (); } } }
优化方案:
将不属于Shape定义的职能移到外面去,保证子类完全满足父类的行为和约束
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class GoodPractice { public static void main (String[] args) { Shape shape = new Rectangle (); double length = 1.3 ; double width = 2.3 ; checkFormat(length); checkFormat(width); Double area = shape.area(length, width); System.out.println(area); double length1 = 1.322 ; double width1 = 2.333 ; checkFormat(length1); checkFormat(width1); area = shape.area(length1, width1); System.out.println(area); } private static void checkFormat (Double value) throws NumberFormatException { String s = Double.toString(value); int index = s.indexOf('.' ); int length = s.substring(index + 1 ).length(); if (length > 2 ) { throw new NumberFormatException (); } } }
I nterface
Segregation Principle(接口隔离原则)
1 Clients should not be forced to depend upon interfaces that they do not use.
模块所依赖的接口,只应提供模块所需的职能,不应该暴露额外用不到的职能;
如果有暴露额外的职能,则需要对接口进行重新设计。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public interface Shape { Integer area () ; void print () ; } public class BadPractice { public static void main (String[] args) { Shape shape = new Rectangle (1 , 2 ); System.out.println(shape.area()); } }
优化方案:对接口的职能进行拆分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class GoodPractice { public static void main (String[] args) { Rectangle rectangle = new Rectangle (1 , 2 ); calculateArea(rectangle); printShape(rectangle); } private static void calculateArea (Shape shape) { System.out.println(shape.area()); } private static void printShape (ShapePrinter shape) { shape.print(); } }
D ependency
Inversion Principl(依赖反转原则)
1 Depend upon abstractions, not concretions
高层模块不应依赖下层模块的实现,而应该依赖于下层模块的抽象。
1 2 3 4 5 6 7 8 9 public class BadPractice { public static void main (String[] args) { MySQL db = new MySQL (); db.connect("localhost:3306" ); } }
重构方案:
提取抽象接口
高层模块依赖于抽象接口进行实现
1 2 3 4 5 6 public class GoodPractice { public static void main (String[] args) { Database db = DatabaseFactory.INSTANCE.getDb(Db.POSTGRE); db.connect("localhost:3306" ); } }
Reference