SOLID是五大面向对象设计原则的缩写。
初始代码有不同的形状shape(圆形,长方形等),累计不同形状面积。
public class Circular { private int length; public int getLength() { return length; } public void setLength(int length) { this.length = length; } }
public class Rectangle { private int length; private int width; public int getWidth() { return width; } public void setWidth(int width) { this.width = width; } public int getLength() { return length; } public void setLength(int length) { this.length = length; } }
参数为List,在for循环中,使用了instanceof方法判断不同的类型,执行不同的面积计算表达式。 public class AreaCalculated { public int sum(List<Object> shape){ int sum = 0; for (Object o : shape) { if (o instanceof Circular){ sum += Math.PI * (((Circular)o).getLength() * ((Circular)o).getLength()); } if (o instanceof Rectangle){ sum += ((Rectangle) o).getLength() * ((Rectangle) o).getWidth(); } } return sum; } } main# public class Main { public static void main(String[] args) { // 创建一个圆形对象 Circular circular = new Circular(); circular.setLength(10); // 创建一个长方形对象 Rectangle rectangle = new Rectangle(); rectangle.setLength(10); rectangle.setWidth(10); // 添加到list中 List<Object> list = ListUtil.of(circular, rectangle); // 累积面积 AreaCalculated areaCalculated = new AreaCalculated(); int sum = areaCalculated.sum(list); System.out.println(sum); } } 准备好上面的代码后,开始solid的原理介绍和具体使用,包括正确使用方法和错误使用方法。 SOLID 单一职责原则(SRP)# 单一职责原则(SRP)表名一个类有且只有一个职责。一个类中可以添加任意的属性和方法,如果添加太多,整个类会显比较笨重,还会产生歧义。 现在我们要为计算出来的面积近行打印,打印json格式或者csv格式。 错误示例# public class AreaCalculated { public int sum(List<Object> shapes){ int sum = 0; for (Object shape : shapes) { if (shape instanceof Circular){ sum += Math.PI * (((Circular)shape).getLength() * ((Circular)shape).getLength()); } if (shape instanceof Rectangle){ sum += ((Rectangle) shape).getLength() * ((Rectangle) shape).getWidth(); } } return sum; } public String json(List<Object> shapes){ return String.format("{sum: %s}", sum(shapes)); } } 在AreaCalculated类中添加了一个json方法,用来见面积结果进行打印,这样也能实现,但是却违背了单一职责原则,而且很容易让程序员产生困惑,AreaCalculated类名的语义是计算面积,但是在里面却有一个打印方法,这就很容易产生歧义。在AreaCalculated类中,我们应该添加面积计算的相关方法,比如:计算一个shape的面积,多个shape的面积,两个面积的差等等方法,总之是有关面积方法,不应该添加其他方法。 正确示例# public class ShapePrinter { public String json(int sum){ return String.format("{sum: %s}", sum); } public String csv(int sum){ return String.format("sum,%s",sum); } } 这里新建了一个ShapePrinter打印类,有关shape打印的方法都应该写在这个类中。这样就做到了单一职责原则,也更好理解,不会出现需要打印方法的时候在AreaCalculated类中去找。 开放封闭原则(OCP)# 开放封闭原则(OCP)指出,一个类应该对扩展开放,对修改关闭。这意味一旦你创建了一个类并且应用程序的其他部分开始使用它,你不应该修改它。为什么呢?因为如果你改变它,很可能你的改变会引发系统的崩溃。 现在我们新增加一个正方形的面积计算代码。 错误示例# public class Square { private int length; public int getLength() { return length; } public void setLength(int length) { this.length = length; } } public class AreaCalculated { public int sum(List<Object> shapes){ int sum = 0; for (Object shape : shapes) { if (shape instanceof Circular){ sum += Math.PI * (((Circular)shape).getLength() * ((Circular)shape).getLength()); } if (shape instanceof Rectangle){ sum += ((Rectangle) shape).getLength() * ((Rectangle) shape).getWidth(); } if (shape instanceof Square){ sum += ((Square) shape).getLength() * ((Square) shape).getLength(); } } return sum; } } 在代码中新添加了一个Square正方形类,在AreaCalculated.sum()方法的for循环中也新添加关于正方形的面积计算表达式。这样做,同样不会影响代码的正常运行,但是却违反了开放封闭原则(OCP),新增加的正方形修改了sum方法的逻辑,如果继续增加其他的形状,那么就需要一直修改sum方法,修改不当还会导致系统奔溃。 正确示例# 新增shape接口 public interface Shape { int sum(); } 圆形,实现shape接口 public class Circular implements Shape{ private int length; public int getLength() { return length; } public void setLength(int length) { this.length = length; } @Override public int sum() { return (int) (Math.PI * (getLength() * getLength())); } } 长方形,实现shape接口 public class Rectangle implements Shape { private int length; private int width; public int getWidth() { return width; } public void setWidth(int width) { this.width = width; } public int getLength() { return length; } public void setLength(int length) { this.length = length; } @Override public int sum() { return getLength() * getWidth(); } } 正方形,实现shape接口 public class Square implements Shape{ private int length; public int getLength() { return length; } public void setLength(int length) { this.length = length; } @Override public int sum() { return getLength() * getLength(); } } 重写面积计算方法 public class AreaCalculated { public int sum(List<Shape> shapes){ int sum = 0; for (Shape shape : shapes) { sum += shape.sum(); } return sum; } } main public class Main { public static void main(String[] args) { Circular circular = new Circular(); circular.setLength(10); Rectangle rectangle = new Rectangle(); rectangle.setLength(10); rectangle.setWidth(10); List<Shape> list = ListUtil.of(circular, rectangle); AreaCalculated areaCalculated = new AreaCalculated(); int sum = areaCalculated.sum(list); ShapePrinter printer = new ShapePrinter(); System.out.println(printer.json(sum)); } } 这里将sum方法抽象成一个接口,每个形状都实现这个接口,重写sum方法。这样在AreaCalculated.sum方法就不需要具体实现,只需要掉对应的形状的sum方法即可,这样就能做到无论新增多少个形状,都不需要去修改以前的代码,只需要在原有的代码上继续扩展即可。 里氏替换原则(LSP)# 里氏替换原则指出,派生的子类应该是可替换基类的,也就是说任何基类可以出现的地方,子类一定可以出现。值得注意的是,当你通过继承实现多态行为时,如果派生类没有遵守LSP,可能会让系统引发异常。 错误示例# NoShape public class NoShape implements Shape{ @Override public int sum() { throw new RuntimeException(); } } main public class Main { public static void main(String[] args) { Circular circular = new Circular(); circular.setLength(10); Rectangle rectangle = new Rectangle(); rectangle.setLength(10); rectangle.setWidth(10); NoShape noShape = new NoShape(); List<Shape> list = ListUtil.of(circular, rectangle, noShape); AreaCalculated areaCalculated = new AreaCalculated(); int sum = areaCalculated.sum(list); ShapePrinter printer = new ShapePrinter(); System.out.println(printer.json(sum)); } } 在这里新增了一个NoShape类,并实现Shape接口,实现sum方法,sum方法直接抛出异常。在main方法中,将NoShape添加进list中,运行得到: Exception in thread "main" java.lang.RuntimeException at com.wuguipeng.platform.solid.NoShape.sum(NoShape.java:10) at com.wuguipeng.platform.solid.AreaCalculated.sum(AreaCalculated.java:14) at com.wuguipeng.platform.solid.Main.main(Main.java:25) 里氏替换原则指出派生的子类应该是可替换基类的,这里NoShape明显不能替换基类,不符合里氏替换原则。 正确示例# NoShape顾名思义,不是形状,那么就不能继承Shape。 接口隔离原则(ISP)# 接口隔离原则(ISP)表明类不应该被迫依赖他们不使用的方法,也就是说一个接口应该拥有尽可能少的行为,它是精简的,也是单一的。 错误示例# Shape新增体积计算 public interface Shape { int sum(); int volume(); } 正方形,其他几个形状忽略,当是都要实现volume方法 public class Square implements Shape{ private int length; public int getLength() { return length; } public void setLength(int length) { this.length = length; } @Override public int sum() { return getLength() * getLength(); } @Override public int volume() { return 0; } } 在Shape接口中新增了一个方法,用来计算体积,但是正方形不是正方体,是不计算不出体积的,实现Shape的方法都将强制实现volume方法。这就违背了接口隔离原则。 正确示例# 新增体积接口 public interface Volume { int volume(); } 三角体 public class TriangularBody implements Shape, Volume{ @Override public int sum() { return 0; } @Override public int volume() { return 0; } } 正方形 public class Square implements Shape{ private int length; public int getLength() { return length; } public void setLength(int length) { this.length = length; } @Override public int sum() { return getLength() * getLength(); } } 在这里新增加了Volume接口并定义了volume方法用了计算体积,如果是一个正方形那么只需要实现Shape接口即可,如果是三角体,那么可以同时实现Shape和Volume接口。 依赖倒置原则(DIP)# 依赖倒置原则(DIP)表明高层模块不应该依赖低层模块,相反,他们应该依赖抽象类或者接口。这意味着你不应该在高层模块中使用具体的低层模块。因为这样的话,高层模块变得紧耦合低层模块。如果明天,你改变了低层模块,那么高层模块也会被修改。 错误示例# 修改ShapePrinter public class ShapePrinter { private final AreaCalculated areaCalculated; public ShapePrinter(AreaCalculated areaCalculated) { this.areaCalculated = areaCalculated; } public String json(List<Shape> shapes){ return String.format("{sum: %s}", areaCalculated.sum(shapes)); } public String csv(List<Shape> shapes){ return String.format("sum,%s",areaCalculated.sum(shapes)); } } 这里修改了ShapePrinter类,在打印的时候才去计算面积,所以注入了AreaCalculated类。这里违法了两个原则,1. 如果修改了AreaCalculated类,那么违法了开放封闭原则(OCP),因为我们强依赖AreaCalculated类并且是个具体的类,不是抽象的,修改可能会导致ShapePrinter遭到破坏。2. ShapePrinter依赖具体的AreaCalculated,而不是抽象的,违法了依赖倒置原则(DIP)。 正确示例# 新增IAreaCalculated接口 public interface IAreaCalculated { int sum(List<Shape> shapes); } 实现接口 public class AreaCalculated implements IAreaCalculated { @Override public int sum(List<Shape> shapes){ int sum = 0; for (Shape shape : shapes) { sum += shape.sum(); } return sum; } } 重写ShapePrinter public class ShapePrinter { private final IAreaCalculated iAreaCalculated; public ShapePrinter(IAreaCalculated iAreaCalculated) { this.iAreaCalculated = iAreaCalculated; } public String json(List<Shape> shapes){ return String.format("{sum: %s}", iAreaCalculated.sum(shapes)); } public String csv(List<Shape> shapes){ return String.format("sum,%s",iAreaCalculated.sum(shapes)); } } main public class Main { public static void main(String[] args) { Circular circular = new Circular(); circular.setLength(10); Rectangle rectangle = new Rectangle(); rectangle.setLength(10); rectangle.setWidth(10); List<Shape> list = ListUtil.of(circular, rectangle); // 具体实现 IAreaCalculated areaCalculated = new AreaCalculated(); ShapePrinter printer = new ShapePrinter(areaCalculated); System.out.println(printer.json(list)); } } 这里ShapePrinter依赖IAreaCalculated接口,不依赖具体实现,在main方法中,将具体实现创建出来,传递给ShapePrinter即可。如果要修改AreaCalculated,只需要重新新建一个类实现IAreaCalculated接口,满足开放封闭原则(OCP)原则,同时满足依赖倒置原则(DIP),不依赖具体实现,依赖接口。
public class AreaCalculated { public int sum(List<Object> shape){ int sum = 0; for (Object o : shape) { if (o instanceof Circular){ sum += Math.PI * (((Circular)o).getLength() * ((Circular)o).getLength()); } if (o instanceof Rectangle){ sum += ((Rectangle) o).getLength() * ((Rectangle) o).getWidth(); } } return sum; } }
public class Main { public static void main(String[] args) { // 创建一个圆形对象 Circular circular = new Circular(); circular.setLength(10); // 创建一个长方形对象 Rectangle rectangle = new Rectangle(); rectangle.setLength(10); rectangle.setWidth(10); // 添加到list中 List<Object> list = ListUtil.of(circular, rectangle); // 累积面积 AreaCalculated areaCalculated = new AreaCalculated(); int sum = areaCalculated.sum(list); System.out.println(sum); } }
准备好上面的代码后,开始solid的原理介绍和具体使用,包括正确使用方法和错误使用方法。
单一职责原则(SRP)表名一个类有且只有一个职责。一个类中可以添加任意的属性和方法,如果添加太多,整个类会显比较笨重,还会产生歧义。
现在我们要为计算出来的面积近行打印,打印json格式或者csv格式。
public class AreaCalculated { public int sum(List<Object> shapes){ int sum = 0; for (Object shape : shapes) { if (shape instanceof Circular){ sum += Math.PI * (((Circular)shape).getLength() * ((Circular)shape).getLength()); } if (shape instanceof Rectangle){ sum += ((Rectangle) shape).getLength() * ((Rectangle) shape).getWidth(); } } return sum; } public String json(List<Object> shapes){ return String.format("{sum: %s}", sum(shapes)); } }
在AreaCalculated类中添加了一个json方法,用来见面积结果进行打印,这样也能实现,但是却违背了单一职责原则,而且很容易让程序员产生困惑,AreaCalculated类名的语义是计算面积,但是在里面却有一个打印方法,这就很容易产生歧义。在AreaCalculated类中,我们应该添加面积计算的相关方法,比如:计算一个shape的面积,多个shape的面积,两个面积的差等等方法,总之是有关面积方法,不应该添加其他方法。
public class ShapePrinter { public String json(int sum){ return String.format("{sum: %s}", sum); } public String csv(int sum){ return String.format("sum,%s",sum); } }
这里新建了一个ShapePrinter打印类,有关shape打印的方法都应该写在这个类中。这样就做到了单一职责原则,也更好理解,不会出现需要打印方法的时候在AreaCalculated类中去找。
开放封闭原则(OCP)指出,一个类应该对扩展开放,对修改关闭。这意味一旦你创建了一个类并且应用程序的其他部分开始使用它,你不应该修改它。为什么呢?因为如果你改变它,很可能你的改变会引发系统的崩溃。
现在我们新增加一个正方形的面积计算代码。
public class Square { private int length; public int getLength() { return length; } public void setLength(int length) { this.length = length; } }
public class AreaCalculated { public int sum(List<Object> shapes){ int sum = 0; for (Object shape : shapes) { if (shape instanceof Circular){ sum += Math.PI * (((Circular)shape).getLength() * ((Circular)shape).getLength()); } if (shape instanceof Rectangle){ sum += ((Rectangle) shape).getLength() * ((Rectangle) shape).getWidth(); } if (shape instanceof Square){ sum += ((Square) shape).getLength() * ((Square) shape).getLength(); } } return sum; } }
在代码中新添加了一个Square正方形类,在AreaCalculated.sum()方法的for循环中也新添加关于正方形的面积计算表达式。这样做,同样不会影响代码的正常运行,但是却违反了开放封闭原则(OCP),新增加的正方形修改了sum方法的逻辑,如果继续增加其他的形状,那么就需要一直修改sum方法,修改不当还会导致系统奔溃。
新增shape接口
public interface Shape { int sum(); }
圆形,实现shape接口
public class Circular implements Shape{ private int length; public int getLength() { return length; } public void setLength(int length) { this.length = length; } @Override public int sum() { return (int) (Math.PI * (getLength() * getLength())); } }
长方形,实现shape接口
public class Rectangle implements Shape { private int length; private int width; public int getWidth() { return width; } public void setWidth(int width) { this.width = width; } public int getLength() { return length; } public void setLength(int length) { this.length = length; } @Override public int sum() { return getLength() * getWidth(); } }
正方形,实现shape接口
public class Square implements Shape{ private int length; public int getLength() { return length; } public void setLength(int length) { this.length = length; } @Override public int sum() { return getLength() * getLength(); } }
重写面积计算方法
public class AreaCalculated { public int sum(List<Shape> shapes){ int sum = 0; for (Shape shape : shapes) { sum += shape.sum(); } return sum; } }
main
public class Main { public static void main(String[] args) { Circular circular = new Circular(); circular.setLength(10); Rectangle rectangle = new Rectangle(); rectangle.setLength(10); rectangle.setWidth(10); List<Shape> list = ListUtil.of(circular, rectangle); AreaCalculated areaCalculated = new AreaCalculated(); int sum = areaCalculated.sum(list); ShapePrinter printer = new ShapePrinter(); System.out.println(printer.json(sum)); } }
这里将sum方法抽象成一个接口,每个形状都实现这个接口,重写sum方法。这样在AreaCalculated.sum方法就不需要具体实现,只需要掉对应的形状的sum方法即可,这样就能做到无论新增多少个形状,都不需要去修改以前的代码,只需要在原有的代码上继续扩展即可。
里氏替换原则指出,派生的子类应该是可替换基类的,也就是说任何基类可以出现的地方,子类一定可以出现。值得注意的是,当你通过继承实现多态行为时,如果派生类没有遵守LSP,可能会让系统引发异常。
NoShape
public class NoShape implements Shape{ @Override public int sum() { throw new RuntimeException(); } }
public class Main { public static void main(String[] args) { Circular circular = new Circular(); circular.setLength(10); Rectangle rectangle = new Rectangle(); rectangle.setLength(10); rectangle.setWidth(10); NoShape noShape = new NoShape(); List<Shape> list = ListUtil.of(circular, rectangle, noShape); AreaCalculated areaCalculated = new AreaCalculated(); int sum = areaCalculated.sum(list); ShapePrinter printer = new ShapePrinter(); System.out.println(printer.json(sum)); } }
在这里新增了一个NoShape类,并实现Shape接口,实现sum方法,sum方法直接抛出异常。在main方法中,将NoShape添加进list中,运行得到:
Exception in thread "main" java.lang.RuntimeException at com.wuguipeng.platform.solid.NoShape.sum(NoShape.java:10) at com.wuguipeng.platform.solid.AreaCalculated.sum(AreaCalculated.java:14) at com.wuguipeng.platform.solid.Main.main(Main.java:25)
里氏替换原则指出派生的子类应该是可替换基类的,这里NoShape明显不能替换基类,不符合里氏替换原则。
NoShape顾名思义,不是形状,那么就不能继承Shape。
接口隔离原则(ISP)表明类不应该被迫依赖他们不使用的方法,也就是说一个接口应该拥有尽可能少的行为,它是精简的,也是单一的。
Shape新增体积计算
public interface Shape { int sum(); int volume(); }
正方形,其他几个形状忽略,当是都要实现volume方法
public class Square implements Shape{ private int length; public int getLength() { return length; } public void setLength(int length) { this.length = length; } @Override public int sum() { return getLength() * getLength(); } @Override public int volume() { return 0; } }
在Shape接口中新增了一个方法,用来计算体积,但是正方形不是正方体,是不计算不出体积的,实现Shape的方法都将强制实现volume方法。这就违背了接口隔离原则。
新增体积接口
public interface Volume { int volume(); }
三角体
public class TriangularBody implements Shape, Volume{ @Override public int sum() { return 0; } @Override public int volume() { return 0; } }
正方形
在这里新增加了Volume接口并定义了volume方法用了计算体积,如果是一个正方形那么只需要实现Shape接口即可,如果是三角体,那么可以同时实现Shape和Volume接口。
依赖倒置原则(DIP)表明高层模块不应该依赖低层模块,相反,他们应该依赖抽象类或者接口。这意味着你不应该在高层模块中使用具体的低层模块。因为这样的话,高层模块变得紧耦合低层模块。如果明天,你改变了低层模块,那么高层模块也会被修改。
修改ShapePrinter
public class ShapePrinter { private final AreaCalculated areaCalculated; public ShapePrinter(AreaCalculated areaCalculated) { this.areaCalculated = areaCalculated; } public String json(List<Shape> shapes){ return String.format("{sum: %s}", areaCalculated.sum(shapes)); } public String csv(List<Shape> shapes){ return String.format("sum,%s",areaCalculated.sum(shapes)); } }
这里修改了ShapePrinter类,在打印的时候才去计算面积,所以注入了AreaCalculated类。这里违法了两个原则,1. 如果修改了AreaCalculated类,那么违法了开放封闭原则(OCP),因为我们强依赖AreaCalculated类并且是个具体的类,不是抽象的,修改可能会导致ShapePrinter遭到破坏。2. ShapePrinter依赖具体的AreaCalculated,而不是抽象的,违法了依赖倒置原则(DIP)。
新增IAreaCalculated接口
public interface IAreaCalculated { int sum(List<Shape> shapes); }
实现接口
public class AreaCalculated implements IAreaCalculated { @Override public int sum(List<Shape> shapes){ int sum = 0; for (Shape shape : shapes) { sum += shape.sum(); } return sum; } }
重写ShapePrinter
public class ShapePrinter { private final IAreaCalculated iAreaCalculated; public ShapePrinter(IAreaCalculated iAreaCalculated) { this.iAreaCalculated = iAreaCalculated; } public String json(List<Shape> shapes){ return String.format("{sum: %s}", iAreaCalculated.sum(shapes)); } public String csv(List<Shape> shapes){ return String.format("sum,%s",iAreaCalculated.sum(shapes)); } }
public class Main { public static void main(String[] args) { Circular circular = new Circular(); circular.setLength(10); Rectangle rectangle = new Rectangle(); rectangle.setLength(10); rectangle.setWidth(10); List<Shape> list = ListUtil.of(circular, rectangle); // 具体实现 IAreaCalculated areaCalculated = new AreaCalculated(); ShapePrinter printer = new ShapePrinter(areaCalculated); System.out.println(printer.json(list)); } }
这里ShapePrinter依赖IAreaCalculated接口,不依赖具体实现,在main方法中,将具体实现创建出来,传递给ShapePrinter即可。如果要修改AreaCalculated,只需要重新新建一个类实现IAreaCalculated接口,满足开放封闭原则(OCP)原则,同时满足依赖倒置原则(DIP),不依赖具体实现,依赖接口。
作者:wuguipeng
出处:https://www.cnblogs.com/wuguipeng/p/16862640.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义