• Java


    作者的原标题是<Prefer class hierarchies to tagged classes>,即用类层次优于tagged class。


    我不知道有没有tagged class这么一说,其实作者指的tagged class的是一个类描述了多种抽象,可以根据某个field决定不同的实例。
    下面是书中例子,使用shape和部分表示长度的field构成形状并计算面积,脑补一下什么是tagged class:

    class Figure {
        enum Shape {
            RECTANGLE, CIRCLE
        };
    
        // Tag field - the shape of this figure
        final Shape shape;
    
        // These fields are used only if shape is RECTANGLE
        double length;
        double width;
    
        // This field is used only if shape is CIRCLE
        double radius;
    
        // Constructor for circle
        Figure(double radius) {
            shape = Shape.CIRCLE;
            this.radius = radius;
        }
    
        // Constructor for rectangle
        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();
            }
        }
    }
    


    不难看出这个类想传达什么信息,也不难看出这样的方式有很多缺陷。
    虽然能看懂是什么意思,但由于各种实现挤在一个类中,其可读性并不好。
    不同的实现放到一个类里描述,即会根据实现的不同而使用不同的field,即,field无法声明为final。(难道要在构造器里处理不用的field?)
    虽然微不足道,内存确实存在毫无意义的占用。
    不够OO。


    虽然上一篇把类层次说得一无是处,其实类层次就是用来解决这一问题的,而上面的tagged class是用非OO的方式模仿类层次。
    将tagged class转为类层次,首先要将tagged class里的行为抽象出来,并为其提供抽象类。
    以上面的Figure为例,我们之需要一个方法——area。
    接下来需要为每一个tag定义具体子类,即例子中的circle和rectangle。
    然后为子类提供相应的field,即circle中的radius和rectangle的width、length。
    最后为子类提供抽象方法的相应实现。
    其实都不用这样去说明转换步骤,因为OO本身就是很自然的东西。


    转换结果如下:

    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;
        }
    }
    
    class Square extends Rectangle {
        Square(double side) {
            super(side, side);
        }
    }
    


    这样做的好处显而易见,
    代码简单清晰,没有样板代码;
    类型相互独立,不会受到无关field的影响,field可以声明为final。
    子类行可以独立进行扩展,互不干扰。


    回到最初的tagged class,它真的就一无是处?
    如果使用这个类,我只需要在调用构造器时使用相应的参数就可以得到想要的实例。
    就像策略模式那样。
    当然,tagged class也许可能算是策略模式(传递的应该是某个行为的特征,而不是实例特征),但策略模式在Java中并不是这样使用。


    通常,一个策略是通过调用者通过传递函数来指定特定的行为的。
    但Java是没有函数指针的,所以我们用对象引用实现策略模式,即调用该对象的方法。
    对于这种仅仅作为一个方法的"载体",即实例等同与方法指针的对象,作者将其称为函数对象(function object)。


    举个例子,比如我们有这样的一个具体策略(concrete strategy):

    class StringLengthComparator {
        private StringLengthComparator() {
        }
    
        public static final StringLengthComparator INSTANCE = new StringLengthComparator();
    
        public int compare(String s1, String s2) {
            return s1.length() - s2.length();
        }
    }
    


    具体策略的引用可以说是一个函数指针。
    对具体策略再抽象一层即成为一个策略接口(strategy interface)。
    对于上面的例子,java.util中正好有Comparator:

    public interface Comparable<T> {
    
        int compare(T o1, T o2);
    
        boolean equals(Object obj);
    }
    


    于是,我们使用的时候可能会用匿名类传递一个具体策略:

    Arrays.sort(stringArray, new Comparator<String>() {
        public int compare(String s1, String s2) {
            return s1.length() - s2.length();
        }
    });
    


    用代码描述似乎是那么回事,我只是在我想使用的地方传递了一个具体策略。
    缺点很明显——每次调用的时候又需要创建一个实例。
    但又能怎么做? 把每个可能用到的具体策略在某个Host类里实现策略接口后都做成field并用final声明?
    感觉很傻,但确实可以考虑,因为这样做还有其他好处。
    比如,相比匿名类,具体策略可以有更直观的命名,而且具体策略可以实现其他接口。


    代码如下:

    // Exporting a concrete strategy
    class Host {
        private static class StrLenCmp
            implements Comparator<String>, Serializable {
                public int compare(String s1, String s2) {
                    return s1.length() - s2.length();
                }
        }
    
        // Returned comparator is serializable
        public static final Comparator<String> STRING_LENGTH_COMPARATOR = new StrLenCmp();
    
        ... // Bulk of class omitted
    }
  • 相关阅读:
    深入理解JavaScript系列
    Knockout应用开发指南(完整版) 目录索引
    js原生设计模式——8单例模式之简约版属性样式方法库
    彻底理解JavaScript原型
    Javascript模块化编程(一):模块的写法
    使用Grunt构建自动化开发环境
    js原生之一个面向对象的应用
    js原生之函数
    angular源码分析:angular中的依赖注入式如何实现的
    js原生之scrollTop、offsetHeight和offsetTop等属性用法详解
  • 原文地址:https://www.cnblogs.com/kavlez/p/4251298.html
Copyright © 2020-2023  润新知