在写代码时,通常都是不那么单一的,同一个类会有各种不同的场景需要判断,比如根据传进去的参数去进行不同的计算,书中给了个例子
public class Figure {
enum Shape {
RECTANGLE,
CIRCLE
}
final Shape shape;
double length;
double width;
double radius;
public Figure(double radius) {
shape = Shape.CIRCLE;
this.radius = radius;
}
public Figure(double length, double width) {
shape = Shape.RECTANGLE;
this.length = length;
this.width = width;
}
double area() {
switch (shape) {
case RECTANGLE:
return length * width;
case CIRCLE:
return Math.PI * (radius * radius);
default:
throw new AssertionError();
}
}
}
这是个类,传入数据,然后求面积。这个类意思是有两种不同的情况,一种是长方形,一种是圆形,两个构造方法,分别对应这两种情况,area() 求面积的这个方法,也是根据传入的参数,辨别是哪种类型,然后做出对应的面积算法,给出结果。 这样写,看似比较好,把计算面积的各种情况都写在了一起,如果改动的话,直接改这个方法就行了,不用四处乱找。 但,这种标签类有很大的缺点。里面逻辑严重耦合,类里面包含着枚举声明,长方形和圆形的长度的属性都有,造成了无谓的浪费,内存增加,同时成员变量不能是final的,违反可变性最小原则。方法的扩展性不好,添加新的东西,一不留神容易该出area() 中的问题,一旦遗漏了 switch 语句,很容易出错,构造方法如果传错了,也会造成错误的结果。综上所述,代码过于冗长,不便于扩展,效率低。
对于这种代码,我们可以进行功能剥离,进行分类。把相同的功能抽取,area()方法,由于长方形和圆形的具体逻辑不一样,那么就抽象到基类,单独写两个子类继承基类,子类里面控制自己的长度属性,这样就避免了不需要的属性的内存浪费了,同时成员变量都可以用final修饰。继承的好处就是建立了一个体系,从上到下的,逻辑清晰,并且扩展第三种形状时,只需要继续继承基类或者这两个子类,不修改原先的代码,不会造成误操作。
abstract class Figure {
abstract double area();
}
class Circle extends Figure {
final double radius;
Circle(double radius) {
this.radius = radius;
}
double area() {
return Math.PI * (radius * radius);
}
}
class Rectangle extends Figure {
final double length;
final double width;
Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
double area() {
return length * width;
}
}
这样属于一个体系,从上到下的,如果我们要再扩展一个梯形 和 正方形,只需要分别继承 Figure 和 Rectangle, 梯形是一个新的形状,正方形是长方形的一种的特殊存在。
class Trapezoid extends Figure{
final double hight;
final double topWidth;
final double bottomWidth;
public Trapezoid(double hight, double topWidth, double bottomWidth) {
this.hight = hight;
this.topWidth = topWidth;
this.bottomWidth = bottomWidth;
}
@Override
double area() {
return (topWidth + bottomWidth) / 2 * hight;
}
}
class Square extends Rectangle{
public Square(double side) {
super(side, side);
}
}
注意上面的话,实际上长方形也可以理解为是梯形的一种特殊存在。那么,可以重新修改代码,
class Rectangle extends Trapezoid {
Rectangle(double length, double width) {
super(length, width, width);
}
}
class Square extends Rectangle{
public Square(double side) {
super(side, side);
}
}
只修改了Rectangle 长方形这个类,其他的保持不变,这么看的话,是不是 感觉 圆形 梯形 长方形 正方形 的面积,构成了一个体系,扩展特别方便,并且逻辑很清晰,如果这时候你还感觉不到好处在哪的话,可以用标签类来实现上面几个形状的面积,比较一下,就明白了。
public class Figure {
enum Shape {
CIRCLE,
RECTANGLE,
TRAPEZOID,
SQUARE
}
final Shape shape;
double radius;
double length;
double width;
double hight;
double topWidth;
double bottomWidth;
double side;
public Figure(double radius) {
shape = Shape.CIRCLE;
this.radius = radius;
}
public Figure(double length, double width) {
shape = Shape.RECTANGLE;
this.length = length;
this.width = width;
}
public Figure(double hight, double topWidth, double bottomWidth) {
shape = Shape.TRAPEZOID;
this.hight = hight;
this.topWidth = topWidth;
this.bottomWidth = bottomWidth;
}
public Figure(double side, Shape shape) {
this.shape = shape;
this.side = side;
}
double area() {
switch (shape) {
case RECTANGLE:
return length * width;
case CIRCLE:
return Math.PI * (radius * radius);
case TRAPEZOID:
return (topWidth + bottomWidth) / 2 * hight;
case SQUARE:
return side * side;
default:
throw new AssertionError();
}
}
}
这个是新的写法,比较一下,就会发现圆形和正方形都是一个参数,如果要通过构造方法的话,要么像上面的那样加参数,要么就传长方形的构造,不灵活,容易出错。上述代码还可以优化,把正方形和长方形都归纳到梯形的里面,有兴趣的可以自己动手一下。但是这样,属性的意义就被颠覆了,我们写代码是为了逻辑清晰,易扩展,如果是提高了新能,但牺牲了可读性和可扩展行,窃以为不值,如果不是到了万不得已的情况,对性能有特别高的要求,不要牺牲清晰的逻辑和扩展性。