[学习笔记]《代码整洁之道》(五)

[学习笔记] 《代码整洁之道》— 第6章 对象和数据结构

将数据设为私有(private)有一个理由:我们不想其他人依赖这些变量。我们还想在心血来潮时自由修改其类型或实现。那么,为什么还有那么多程序员给对象自动添加赋值器和取值器,将私有变量公之于众、如同它们根本就是共有变量一般呢?

数据抽象

具象点:非常清楚第是在矩形坐标系中实现。

public class Point{
    public double x;
    public double y;
}

抽象点:不知道该实现会是在矩形坐标系中,还是极坐标系中。

public interface Point{
    double getX();
    double getY();
    void setCartesian(double x, double y);
    double getR();
    double getTheta();
    void setPolar(double r, double theta)
}
  • 隐藏实现并非只是在变量之间放上一个函数层那么简单。隐藏实现关乎抽象!
    • 类并不简单地用取值器和赋值器将其变量推向外间,而是曝露抽象接口,以便用户无需了解数据的实现就能操作数据本体
  • 要以最好的方式呈现某个对象包含的数据,需要做严肃的思考。
  • 傻乐着乱加取值器和赋值器,是最坏的选择。

数据、对象的反对称性

  • 对象
    • 把数据隐藏在抽象之后
    • 曝露操作操作数据的函数
  • 数据结构
    • 曝露其数据
    • 没有提供有意义的函数
  • 它们是对立的!
/* 过程式形状代码 */
public class Square{
    public Point topLeft;
    public double side;
}

public class Rectangle{
    public Point topLeft;
    public double height;
    public double width;
}

public class Circle{
    public Piont center;
    public double radius;
}

public class Geometry{
    public final double PI = 3.141592653589793;
    
    public double area(Object shape) throw NoSuchShapeException
    {
        if(shape instanceof Square){
            Square s = (Square)shape;
            return s.side * s.side;
        }
        else if(shape instanceof Rectangle){
            Square r = (Recangle)shape;
            return r.height * r.width;
        }
        else if(shape instanceof Circle){
            Square c = (Circle)shape;
            return PI * c.radius * c.radius;
        }
        throw new NoSuchShapeException();
    }
}

// 如果给Geometry类添加一个primeter()函数,那些形状类根本不会受它影响。
// 如果添加加一个新形状,就得修改Geometry中的所有函数来处理它。
/* 多态式形状代码 */
public class Square implements Shape{
    private Point topLeft;
    private double side;
    
    public double area(){
        return side*side;
    }
}

public class Rectangle implements Shape{
    private Point topLeft;
    private double height;
    private double width;
    
    public double area(){
        return height * width;
    }
}

public class Circle implements Shape{
    private Point center;
    private double radius;
    public final double PI = 3.141592653589793;
    
    public double area(){
        return PI * radius * radius;
    }
}

// area()方法是多态的。
// 如果添加一个新形状,现有函数一个也不会受影响。
// 如果添加一个新函数的话,所有形状都得做修改。

对象与数据结构之间的二分原理:

  • 过程式代码(使用数据结构的代码)便于在不改动既有数据结构的前提下添加新函数。面向对象代码便于在不改动既有函数的前提下添加新类。
    • 过程式代码难以添加新的数据结构,因为必须修改所有函数。
    • 面向对象代码乃以添加新函数,因为必须修改所有类。

得墨忒耳律

  • 得墨忒耳律:模块不应了解它所操作对象的内部情形。

    • 对象不应通过存取器曝露其内部结构。
  • 类 C 的方法 f 只应该调用以下对象的方法:

    • C;
    • 由 f 创建的对象;
    • 作为参数传递给 f 的对象;
    • 由C的实体变量持有的对象。
  • 方法不应调用有任何函数返回的对象的方法。

  • 火车失事:这类连串的调用通常被认为是肮脏的风格

    final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
    

    最好做如下的切分:

    Options opts = ctxt.getOptions();
    File scratchDir = opts.getScratchDir();
    final String outputDir = scratchDir.getAbsolutePath();
    
  • 混杂:一半是对象,一半是数据结构。

    • 既增加了添加新函数的难度,也增加了添加新数据结构的难度。
  • 隐藏结构

    • 假如 ctxt、Options 和 ScratchDir 是拥有真实行为的对象呢?

      • 我们不应该能够看到内部结构,如何才能取得临时目录的绝对路径呢?

        ctxt.getAbsolutePathOfScratchDirectoryOption();
        // 可能导致ctxt对象中方法的曝露
        

        或者

        ctxt.getScratchDirectoryOption().getAbsolutePath();
        // 假设getScratchDirectoryOption()返回一个数据结构而非对象
        

        所以,直接让 ctxt 对象来做这件事!

        BufferedOuputStream bos = ctxt.createScratchFileStream(classFileName);
        // ctxt隐藏了其内部结构,防止当前函数因为浏览它不该知道的对象而违反得墨忒耳律
        

数据传送对象

  • 最为精炼的数据结构,是一个只有公共变量、没有函数的类:数据传送对象 (DTO:Data Transfer Objects)

    • DTO是非常有用的结构,尤其是在与数据库通信、或解析套接字传递的消息之类的场景中。

    • 豆结构(“bean”结构)

      • 拥有由赋值器和取值器操作的私有变量。

        public class Address{
            private String street;
            private String streetExtra;
            private String city;
            private String state;
            private String zip;
            
            public Address(String street, String streetExtra, 
                           String city, String state, String zip){
                this.street = street;
                this.streetExtra = streetExtra;
                this.city = city;
                this.state = state;
                this.zip = zip;
            }
            
            public String getStreet(){
                return street;
            }
            
            public String getStreetExtra(){
                return streetExtra;
            }
            
            public String getCity(){
                return city;
            }
            
            public String getState(){
                return state;
            }
            
            public String getZip(){
                return zip;
            }
        }
        
        • Active Record 是一种特殊的DTO形式
          • 拥有公共(或可豆式访问的)变量的数据结构,通常也会拥有类似 save 和 find 这样的可浏览方法。
          • 一般是用于对数据库表后其他数据源的直接翻译。

小结

  • 对象曝露行为,隐藏数据。便于添加新对象类型而无需修改既有行为,同时也难以在既有对象中添加新行为。
  • 数据结构曝露数据,没有明显的行为。便于向既有数据结构添加新行为,同时也难以向既有函数添加新数据结构。

参考文献

[1] Robert C. Martin 著,韩磊 译,《代码整洁之道》,北京:人民邮电出版社,2010.1(2018.9 重印), ISBN 978-7-115-21687-8。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值