第二十条 类层次优于标签类

在写代码时,通常都是不那么单一的,同一个类会有各种不同的场景需要判断,比如根据传进去的参数去进行不同的计算,书中给了个例子

    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();
            }
        }
    }

这个是新的写法,比较一下,就会发现圆形和正方形都是一个参数,如果要通过构造方法的话,要么像上面的那样加参数,要么就传长方形的构造,不灵活,容易出错。上述代码还可以优化,把正方形和长方形都归纳到梯形的里面,有兴趣的可以自己动手一下。但是这样,属性的意义就被颠覆了,我们写代码是为了逻辑清晰,易扩展,如果是提高了新能,但牺牲了可读性和可扩展行,窃以为不值,如果不是到了万不得已的情况,对性能有特别高的要求,不要牺牲清晰的逻辑和扩展性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值