,在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),不依赖具体实现,依赖接口。