# 8.2 访问控制——代码的多级管理

# 重用的度

合理的重用既可以节省开发时间,又节省了维护时间,代码也显得更简洁优美。

但是过犹不及,过度重用也可能会造成滥用和误用。
	一方面,开发者容易沉溺于局部重用的妙处而忽略整体设计,淡忘开发类最核心的职责。
	另一方面,一旦所重用的类或方法发生改变,所有的重用者均受牵连,先前节省的时间会加倍偿还。

软件的可重用性建立在应变性的基础之上。二者出现矛盾需要作取舍时,应该以后者为重。
或者说,没有足够的应变能力,不够资格被重用。

# ‘度’

任何一门技艺到了高级阶段,都是‘度’的学问。
初级程序员的理想是为所欲为——能用编程解决一切问题
中级程序员的理想是尽善而为——追求最佳解决方案
高级程序员的理想是有所为有所不为——重在整体设计的选择,能抵制局部技巧的诱惑
最高理想是无为而无不为——无论宏观设计还是微观实现,均非刻意选择,却自然合度。

# 访问修饰符

  • C++ Java C# 语言实现
无限制
	C++ Java C# public
子类或同一包
	C++ 无 、Java protected 、C# protected internal
同一类或子类
	C++ protected、Java private protected(discard)、C#	protected
子类或同一包
	C++ 无、Java package(default)、 C# internal
同一类
	C++ private(default)、Java private 、C# private(default)
访问修饰符(access modifier)除了应用于类的成员外,在Java 和 C#中还能应用于整个类。

Java 和 C# 比C++多了包的概念,Java的protected相当于C#的protected internal,
不仅可以访问同类和子类,还能被同一包的任何类访问。
而C++和C#中的protected只能被同类和子类访问,相当于Java中昙花一现的 private protected。
  • 选择访问修饰符的原则
基本原则是尽可能的使用限制性更强的修饰符。即使以后改变注意,在放宽也不迟。
	相反的,将一个修饰符收窄就要估计对现存客户的影响。

尤其是域成员,没有特殊的理由都应该是priavte,除非类是一个用作存储的具体数据类型、private内部类或域是一个静态常量。
	域成员代表对象的状态,从运行方面看,若外界随意读取和改动,可能会破坏不变量(invariant),无法保证内在逻辑的一致性;
	从设计方面看,属结构性信息,极易变化;
	从接口方面看,公开接口都是以方法而非域的形式出现的,这些都要求隐藏域成员。
  • 如果将类看做是一个服务者,他向不同范围内的客户承诺不同的服务,或者说是提供了层次化的服务。
public 方法成员向所有类提供服务,
protected 方法成员对该类的子类提供服务,
private 方法成员则只对该类本身提供服务。

# 嵌套类

在一个类的内部定义另一个类,我们称之为嵌套类(nested class),或者嵌套类型。

之所以引入这样一个嵌套类,往往是因为外围类需要使用嵌套类对象作为底层实现,
并且该嵌套类只用于外围类的实现,且同时可以对用户隐藏该底层实现。

# C++中的friend修饰符

一个类与友类或友函数是联合关系而非主客关系,它们之间的互访和普通类内部成员的互访没有本质的区别。
甚至由于friend的单向授权,它反而可以看做private的一种细化。(P231)
访问控制的要害在于合理的控制关联代码的分布,而非单纯地越严格越好。

# 一个类的方法能否访问除了this之外的其他同类对象的private成员?

访问控制是对静态代码的控制,而不是对动态代码的控制。所以它是以代码而不是对象为边界的。

<?php
Class Test {
    private $x;

    public function __construct($xx){
        $this->x = $xx;
    }

    public function output(){
        $this->printX();
        $this->printXIm();
    }

    private function printXIm(){
        $obj = new self($this->x + 1);
        $obj->printX();
    }

    private function printX(){
        printf("%s\n", $this->x);
    }
}

// Test
$objTest = new Test(1);
$objTest->output();

?>

以上代码 正常 输出:
1
2

# 总结

* 从抽象的角度来看,访问控制划分了抽象的边界
一方面从语义上明确抽象的层次化
	越公开的成员越接近抽象接口,越远离具体实现

另一方面从语法上实行双向保护
	——既保护实现代码不受客户代码入侵,也保护客户代码不受实现代码变更的影响

* 从软件应变的角度来看,访问控制划分了代码修改的边界

由此,访问控制越松的成员,辐射范围越广,软件重用的效率越高,
承担的责任越大,修改的代价也越大,因而变化的可能性应该越小。

成熟的程序员对public和protected接口的设计一定慎之又慎,
往往在其上花费的工夫更甚于具体代码的编写。
  • 客户意识
这里的客户不是指软件终端的消费者,而是软件中间消费或重用者,
即调用该软件的代码,有时也指相应代码的编写者。

客户意识对于一个程序员的重要性,丝毫不亚于对一个企业的重要性。

如何培养?
	轻诺者,必寡信。每一个public类,每一个非private成员,都是一份承诺。
	没有明确职责、没有准备承担变更后果之前,请采用最严格的访问控制。

有了客户意识,才有接口责任感。

通过合理的运用访问控制,将类的接口层次化、职责层次化、服务层次化,从而将客户相应地层次化。

千万不要追求廉价的重用而轻易扩大范围接口,
莫以自身之便而致客户之不便,莫以一时之便而致长期之不便。

另外,单元测试对培养客户意识很有帮助,不仅能发现代码的逻辑缺陷还能发现代码的设计缺陷。
因为单元测试代码是最典型的客户代码,它能让你站在客户的角度重新审视自己的接口设计。