zoukankan      html  css  js  c++  java
  • 代码整洁之道-对象和数据结构

    代码整洁之道

    第 6 章 对象和数据结构

    6.2 数据、对象的反对称性

    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 Point center;
        public double radius;
    }
    
    public class Geometry {
        public final double PI = 3.141592653589793;
        
        public double area(Object shape) throws NoSuchShapeException {
            if (shape instanceof Square) {
                Square s = (Square)shape;
                return s.side * s.side;
            }
            else if (shape instanceof Rectangle) {
                Rectangle r = (Rectangle)shape;
                return r.height * r.width;
            }
            else if (shape instanceof Circle) {
                Circle c = (Circle)shape;
                return PI * c.radius * c.radius;
            }
            throw new NoSuchShapeException();
        }
    }
    
    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;
        }
    }
    

    对象把数据隐藏于抽象之后,暴露操作数据的函数。
    数据结构暴露其数据,没有提供有意义的函数。

    添加一个perimeter()函数会怎样?

    添加一个新形状呢?

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

    过程式代码(使用数据结构的代码)便于在不改动既有数据结构的前提下添加新函数。面向对象代码便于在不改动既有函数的前提下添加新类。

    反过来讲也说得通:

    过程式代码难以添加新的数据结构,因为必须修改所有函数。面向对象代码难以添加新函数,因为必须修改所有类。

    在任何系统中,我们有时会希望能够灵活的添加新数据类型,所以更喜欢在这部分使用对象。另外一些时候,我们希望能灵活的添加新行为,这时我们更喜欢使用数据结构和过程。

    6.4 数据传送对象

    最为精炼的数据结构,是一个只有公共变量、没有函数的类。这种数据结构有时被称为数据传送对象,或DTO(Data Transfer Objects)DTO是非常有用的结构,尤其是在与数据库通信、或解析套接字传递的消息之类场景中。

    更常见的是“豆”(bean)结构。豆结构拥有由赋值器和取值器操作的私有变量。

    对豆结构的半封装会让某些OO纯化论者感觉舒服些,不过通常没有其他好处。

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

    Java的访问级别修饰符

    作用域 当前类 同一package 子孙类 其他package
    public
    protected ×
    default × ×
    private × × ×

    C#访问级别修饰符

    访问修饰符 访问级别
    public 任何地方都可以访问
    protected internal 同时具备protected与internal的访问权限
    internal 当前程序集内部可以访问
    protected 当前类内部及其子类都可以访问
    private 只能在当前类内部访问

    将变量设置为私有(private)
    不想其他人依赖这些变量,随时修改其类型或实现。

    6.1 数据抽象

    以下两段代码都表示笛卡尔平面上的一个点。

    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 interface Vehicle {
        double getFuelTankCapacityInGallons();
        double getGallonsOfGasoline();
    }
    
    public interface Vehicle {
        double getPercentFuelRemaining();
    }
    

    前者使用具象手段与机动车的燃料层通信,后者采用百分比抽象。

    你能确定前者里面都是些变量存取器,却无法得知后者中的数据形态。

    不应暴露数据细节,应以抽象形态表述数据。这并不只是用接口或者赋值器、取值器就万事大吉。要做严肃的思考,以最好的方式呈现某个对象包含的数据。

    6.3 得墨忒耳律(迪米特法则)(The Law of Demeter)

    模块不应了解它所操作对象的内部情形。

    得墨忒耳率认为,类 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是个对象,就应该要求它做点什么,而不是要求它给出内部情形。

    那我们为何还要得到临时目录的绝对路径呢?

    来看看同一模块(许多行之后)的这段代码:

    String outFile = outputDir + "/" + className.replace('.','/') + ".class";
    FileOutputStream fout = new FileOutputStream(outFile);
    BufferedOutputStream bos = new BufferedOutputStream(fout);
    

    我们发现,取得临时目录绝对路径的初衷是为了创建指定名称的临时文件。

    所以,直接让ctxt对象来做这事如何?

    BufferedOutputStream bos = ctxt.createScratchFileStream(classFileName);
    

    ctxt隐藏了其内部结构,防止当前函数因浏览它不该知道的对象而违反得墨忒耳律。

    人可以命令一条狗行走,但是不应该直接指挥狗的腿行走,应该由狗去指挥控制它的腿如何行走。

    一个对象应该对其他对象有最少的了解,所以又叫做最少知识原则(Least Knowledge Principle, LKP)。

    降低类之间的耦合。由于每个对象尽量减少对其他对象的了解,因此,很容易使系统的功能模块独立,相互之间不存在(或很少存在)依赖关系。

    1. 只和直接的朋友交流(从依赖者的角度来说,只依赖应该依赖的对象)
    2. 减少对朋友的了解(从被依赖者的角度来说:只暴露应该暴露的方法或者属性,即在编写相关的类的时候确定方法/属性的权限)

    关闭计算机

    当我们按下计算机的关机按钮的时候,计算机会执行一系列的动作:保存未完成的任务、关闭服务、关闭显示器、关闭电源等等。

    public class System {
        public void saveTask() {
            
        }
        
        public void closeService() {
            
        }
        
        public void closeScreen() {
            
        }
        
        public void closePower() {
            
        }
        
        public void close() {
            saveTask();
            closeService();
            closeScreen();
            closePower();
        }
    }
    
    public class Container {
        private System system;
        
        public System getSystem() {
            return system;
        }
    }
    
    public class Person {
        private Container container;
        
        public void clickCloseButton() {
            System system = container.getSystem();
            
            system.closePower();
        }
    }
    
    public class System {
        private void saveTask() {
            
        }
        
        private void closeService() {
            
        }
        
        private void closeScreen() {
            
        }
        
        private void closePower() {
            
        }
        
        public void close() {
            saveTask();
            closeService();
            closeScreen();
            closePower();
        }
    }
    
    public class Container {
        private System system;
        
        public void sendCloseCommand() {
            system.close();
        }
    }
    
    public class Person {
        private Container container;
        
        public void clickCloseButton() {        
            container.sendCloseCommand();
        }
    }
    

    迪米特法则的核心观念就是类间解耦,弱耦合。只有弱耦合了之后,类的复用才可以提高,类变更的风险才可以减低。但解耦是有限度的,不存在没有耦合的系统。所以在实际项目中,需要适度地参考这个原则,避免过犹不及。

  • 相关阅读:
    JD笔试试题(凭记忆写的+人生感悟 try finally )
    ZOJ 3626 Treasure Hunt I(树形dp)
    Oracle数据库有用函数
    leetcode
    BIEE11G系统数据源账号过期问题(默认安装步骤)
    class文件结构浅析(2)
    使用Lua 局部变量来优化性能,同一时候比較局部变量和全局变量
    Linux基本配置和管理 3 ---- Linux命令行文本处理工具
    android面试题及答案
    CentOS 6.4的安装--史上最全-CRPER木木
  • 原文地址:https://www.cnblogs.com/fanful/p/13364379.html
Copyright © 2011-2022 走看看