第十三條、使類和成員的可訪問(wèn)性最小化
設(shè)計(jì)良好的模塊會(huì)隱藏所有的實(shí)現(xiàn)細(xì)節(jié),把它的API和它的實(shí)現(xiàn)清晰地隔離開來(lái)。然后模塊之間只通過(guò)它們的API進(jìn)行通信,一個(gè)模塊不需要知道其他模塊的內(nèi)部工作情況。(信息隱藏(infomation hiding)和封裝(encapsulation))
好處:可以有效地解除組成系統(tǒng)的各模塊之間的耦合關(guān)系,使得這些模塊可以獨(dú)立地開發(fā)、測(cè)試、優(yōu)化、使用、理解和修改。
java的訪問(wèn)機(jī)制決定了類、接口和成員的可訪問(wèn)性。實(shí)體的可訪問(wèn)性是由該實(shí)體聲明所在的位置以及訪問(wèn)修飾符共同決定的。對(duì)于頂層(非嵌套)的類和接口,只有兩種可能的訪問(wèn)級(jí)別:包級(jí)私有(package-private)和公有的(public),如果用public修飾符聲明了頂層類或者接口,那他就是公有的,否則它將是包級(jí)私有的。通過(guò)把類做成包級(jí)私有,它實(shí)際上成了這個(gè)包的實(shí)現(xiàn)部分,而不是該包導(dǎo)出API的一部分。如果一個(gè)包級(jí)私有的頂層類或者接口只是在某一個(gè)類的內(nèi)部被用到,就應(yīng)該考慮使它成為唯一使用它的那個(gè)類的私有嵌套類。
-
對(duì)于成員(域、方法、嵌套類和嵌套接口)有四種可能的訪問(wèn)級(jí)別:
- 私有的private:只有在聲明該成員的頂層類內(nèi)部才可以訪問(wèn)這個(gè)成員。
- 包級(jí)私有的:聲明該成員的包內(nèi)部的任何類都可以訪問(wèn)這個(gè)成員。“缺省default的訪問(wèn)級(jí)別”
- 受保護(hù)的protected:聲明該成員的類的子類可以訪問(wèn)這個(gè)成員,且聲明該成員的包內(nèi)部的任何類也可以訪問(wèn)這個(gè)成員。受保護(hù)的成員是類的導(dǎo)出的API的一部分,必須永遠(yuǎn)得到支持。導(dǎo)出的類的受保護(hù)成員也代表了該類對(duì)于某個(gè)實(shí)現(xiàn)細(xì)節(jié)的公開承諾,應(yīng)該盡量少用。
- 公有的public:在任何地方都可以訪問(wèn)。
如果方法覆蓋了超類中的一個(gè)方法,子類中的訪問(wèn)級(jí)別就不允許低于超類中的訪問(wèn)級(jí)別。這樣可以確保任何可使用超類實(shí)例的地方也都可以使用子類的實(shí)例。如果一個(gè)類實(shí)現(xiàn)了一個(gè)接口,那么接口中的所有類方法在這個(gè)類中也都必須被聲明為公有的。(因?yàn)?strong>接口中所有的方法都隱含著公有訪問(wèn)級(jí)別。)
除了公有靜態(tài)final域的特殊情況之外,公有類都不應(yīng)該包含公有域,并且要確保公有靜態(tài)final域所引用的對(duì)象都是不可變的。
第十四條、在公有類中使用訪問(wèn)方法而非公有域
應(yīng)該用包含私有域和公有訪問(wèn)/設(shè)置方法的類帶進(jìn)行封裝。
第十五條、使可變性最小化
不可變類只是其實(shí)例不能被修改的類。每個(gè)實(shí)例中包含的信息都必須在創(chuàng)建該實(shí)例的時(shí)候提供,并在對(duì)象的整個(gè)生命周期內(nèi)固定不變。(比如:String、基本類型的包裝類、BigInteger和BigDecimal)
不可變類的優(yōu)點(diǎn):更加易于設(shè)計(jì)、實(shí)現(xiàn)和使用,不容易出錯(cuò)且更加安全。-
使類成為不可變遵循的五條規(guī)則:
- 不要提供任何會(huì)修改對(duì)象狀態(tài)的方法(mutator);
- 保證類不會(huì)被擴(kuò)展。
- 使所有的域都是final的。
- 使所有的域都成為私有的。
- 確保對(duì)于任何可變組件的互斥訪問(wèn)。
-
一個(gè)類的實(shí)例:
/** * Created by laneruan on 2017/6/7. * 這個(gè)類表示一個(gè)復(fù)數(shù)。 * 這些算術(shù)運(yùn)算都是創(chuàng)建并返回新的Complex實(shí)例,而不是修改這個(gè)實(shí)例的做法。 * 這種被稱為函數(shù)的做法,因?yàn)檫@些方法返回惡一個(gè)函數(shù)的結(jié)果,這些函數(shù)對(duì)操作數(shù)進(jìn)行運(yùn)算但不修改它。 * 對(duì)應(yīng)的是過(guò)程式或命令式的做法。 */ public class Complex { private final double re; private final double im; //對(duì)于頻繁使用的值,為他們提供公有的的靜態(tài)常量。 public static final Complex ZERO = new Complex(0,0); public static final Complex ONE = new Complex(1,0); public static final Complex I = new Complex(0,1); public Complex(double re,double im){ this.re = re ; this.im = im ; } //使類變成final的一種方式 // private Complex(double re,double im){ // this.re = re ; // this.im = im ; // } public static Complex valueOf(double re,double im){ return new Complex(re,im); } // Accessors with no corresponding mutators //基于極坐標(biāo)創(chuàng)建復(fù)數(shù) public static Complex valueOfPolar(double r,double theta){ return new Complex(r * Math.cos(theta),r * Math.sin(theta)); } public double realPart(){return re;} public double imaginaryPart(){return im;} public Complex add(Complex c){ return new Complex(re + c.re,im + c.re); } public Complex subtract(Complex c) { return new Complex(re - c.re,im - c.im); } public Complex multiply(Complex c){ return new Complex(re*c.re-im*c.im,re*c.im+im*c.re); } public Complex divide(Complex c){ double tmp = c.re * c.re + c.im * c.im; return new Complex((re*c.re+im*c.im)/tmp,(im*c.re-re*c.im)/tmp); } @Override public boolean equals(Object o) { if(o == this){ return true; } if(!(o instanceof Complex)){ return false; } Complex c = (Complex) o ; return Double.compare(re,c.re) == 0 && Double.compare(im,c.im) == 0; } @Override public int hashCode(){ int result = 17 + hashDouble(re); result = 31 * result + hashDouble(im); return result; } private int hashDouble(double val){ long longBits = Double.doubleToLongBits(val); return (int)(longBits ^ (longBits >>>32)); } @Override public String toString(){ return "(" + re + "+" + im+"i)"; } }這個(gè)類表示一個(gè)復(fù)數(shù)。這些算術(shù)運(yùn)算都是創(chuàng)建并返回新的Complex實(shí)例,而不是修改這個(gè)實(shí)例的做法。這種被稱為函數(shù)式的做法,因?yàn)檫@些方法返回了一個(gè)函數(shù)的結(jié)果,這些函數(shù)對(duì)操作數(shù)進(jìn)行運(yùn)算但不修改它。對(duì)應(yīng)的是過(guò)程式或命令式的做法。
這種函數(shù)方法的優(yōu)點(diǎn)帶來(lái)了不可變性,不可變對(duì)象只有一種狀態(tài),即被創(chuàng)建時(shí)的狀態(tài)。不可變對(duì)象本質(zhì)上是線程安全的,不要求同步。當(dāng)多個(gè)線程并發(fā)訪問(wèn)這樣的對(duì)象,不會(huì)發(fā)生破壞,所以不可變對(duì)象可以被自由地共享。不可變對(duì)象為其他對(duì)象提供了大量的構(gòu)件,如果知道一個(gè)復(fù)雜對(duì)象內(nèi)部的組件不會(huì)改變,要維護(hù)它的不變性約束是比較容易的。
不可變類的真正唯一缺點(diǎn)在于:對(duì)于每個(gè)不同的值都需要一個(gè)單獨(dú)的對(duì)象。
-
如何使不可變類自身不被子類化?除了使類成為final外,讓類的所有構(gòu)造器都變成私有的或者包級(jí)私有的,并添加共有的靜態(tài)工廠來(lái)代替公有的構(gòu)造器。以Complex為例:
//這種方式雖不常用,但是最靈活。而且可以肯定不能擴(kuò)展。 private Complex(double re,double im){ this.re = re ; this.im = im ; } public static Complex valueOf(double re,double im){ return new Complex(re,im); } 有關(guān)不可變類的序列化:
實(shí)現(xiàn)Serializable接口,并且它包含一個(gè)或者多個(gè)指向可變對(duì)象的域,就必須提供一個(gè)顯式的readObject或者readResolve方法,或者使用ObjectOutPutStream.writeUnshared和ObjectInputStream.readUnshared方法,即使默認(rèn)的序列化形式是可接受的。
第十六條、復(fù)合優(yōu)先于繼承
繼承(inheritance)是實(shí)現(xiàn)代碼重用的有力手段,但是使用不當(dāng)會(huì)導(dǎo)致軟件變得很脆弱,在包的內(nèi)部使用繼承是非常安全的,在那里,子類和父類的實(shí)現(xiàn)都處在同一個(gè)程序員的控制下。然而,對(duì)于普通的具體類進(jìn)行跨越包邊界的繼承,則是非常危險(xiǎn)的。繼承打破了封裝性,子類依賴于父類中特定功能的實(shí)現(xiàn)細(xì)節(jié)。父類的實(shí)現(xiàn)有可能會(huì)隨著發(fā)行版本的不同而有所變化,子類可能隨之遭到破壞,因此子類也必須隨著父類的更新而演變。
-
導(dǎo)致子類脆弱的一個(gè)相關(guān)原因是:它們的父類在后續(xù)發(fā)行版本中可以獲得新的方法。這些問(wèn)題都來(lái)源于覆蓋(overriding)動(dòng)作。下面是一個(gè)脆弱的實(shí)例:
import java.util.Arrays; import java.util.Collection; import java.util.HashSet; /** * Created by laneruan on 2017/6/7. * 需要查詢HashSet。看看自從它從被創(chuàng)建以來(lái)曾經(jīng)添加了多少個(gè)元素。 * HashSet類中添加元素的方法:add和addAll,因此這兩個(gè)方法都要覆蓋,但并不能正常工作。 * 因?yàn)樵贖ashSet的內(nèi)部,addAll方法是基于add實(shí)現(xiàn)的,所以通過(guò)addAll方法增加的每個(gè)元素都計(jì)算了兩次。 * 此時(shí)可以通過(guò)去掉addAll的覆蓋方法來(lái)修正這個(gè)問(wèn)題,但是這是十分脆弱的,它的功能正確性是需要依賴于HashSet的addAll方法是在 * add方法上實(shí)現(xiàn)的,不能保證隨著發(fā)行版本的不同而不發(fā)生變化。所以此時(shí)的InstrumentedHashSet是十分脆弱的。 */ //Broken - Inappropriate use of inheritance public class InstrumentedHashSet<E> extends HashSet<E> { private int addCount = 0 ; public InstrumentedHashSet(){} public InstrumentedHashSet(int initCap,Float loadFactor){ super(initCap,loadFactor); } @Override public boolean add(E e){ addCount ++; return super.add(e); } @Override public boolean addAll(Collection<? extends E> c){ addCount += c.size(); return super.addAll(c); } public int getAddCount(){ return addCount; } public static void main(String[] args){ InstrumentedHashSet<String> s = new InstrumentedHashSet<String>(); s.addAll(Arrays.asList("Snap","Pop","Crackle")); System.out.println(s.getAddCount());//打印出來(lái)是6? } } -
復(fù)合(composition):不用擴(kuò)展現(xiàn)有的類,而是在新的類中增加一個(gè)私有域,它引用現(xiàn)有類的一個(gè)實(shí)例。因?yàn)楝F(xiàn)有的類變成了新類的一個(gè)組件。新類中的每個(gè)實(shí)例方法都可以調(diào)用被包含的現(xiàn)有類實(shí)例中對(duì)應(yīng)的方法,并返回它的結(jié)果。這被稱為轉(zhuǎn)發(fā)(forwarding),新類中的方法被稱為轉(zhuǎn)發(fā)方法。這樣新得到的類會(huì)非常穩(wěn)固,不依賴于現(xiàn)有類的實(shí)現(xiàn)細(xì)節(jié)。下面是上面脆弱的實(shí)例的復(fù)合版本:
import java.util.*; /** * Created by laneruan on 2017/6/8. * Set接口的存在使得InstrumentedSet類的設(shè)計(jì)成為可能,因?yàn)镾et類保存了HashSet類的功能特性。 * 從本質(zhì)上來(lái)說(shuō)這個(gè)類把一個(gè)Set變成了一個(gè)增加計(jì)數(shù)功能的Set。 * 因?yàn)槊總€(gè)InstrumentedSet實(shí)例都把另一個(gè)Set實(shí)例包裝起來(lái)了,所以稱為wrapper class */ //Wrapper class 包裝類 public class InstrumentedSet<E> extends ForwardingSet<E>{ private int addCount = 0; public InstrumentedSet(Set<E> s){ super(s); } @Override public boolean add(E e){ addCount++; return super.add(e); } @Override public boolean addAll(Collection<? extends E> c){ addCount += c.size(); return super.addAll(c); } public int getAddCount(){ return addCount; } public static void main(String[] args){ InstrumentedSet<String> s = new InstrumentedSet<String>(new HashSet<String>()); s.addAll(Arrays.asList("Snap","Pop","Crackle")); System.out.println(s.getAddCount());//打印出來(lái)是3 System.out.println(s); } } //Reusable forwarding class class ForwardingSet<E> implements Set<E>{ private final Set<E> s; public ForwardingSet(Set<E> s){this.s = s;} @Override public int size() { return s.size(); } @Override public boolean isEmpty() { return s.isEmpty(); } @Override public boolean contains(Object o) { return s.contains(o); } @Override public Iterator<E> iterator() { return s.iterator(); } @Override public Object[] toArray() { return s.toArray(); } @Override public <T> T[] toArray(T[] a) { return s.toArray(a); } @Override public boolean add(E e) { return s.add(e); } @Override public boolean remove(Object o) { return s.remove(o); } @Override public boolean containsAll(Collection<?> c) { return s.containsAll(c); } @Override public boolean addAll(Collection<? extends E> c) { return s.addAll(c); } @Override public boolean retainAll(Collection<?> c) { return s.retainAll(c); } @Override public boolean removeAll(Collection<?> c) { return s.removeAll(c); } @Override public void clear() { s.clear(); } @Override public boolean equals(Object o) { return s.equals(o); } @Override public int hashCode() { return s.hashCode(); } @Override public String toString() { return s.toString(); } } 什么時(shí)候使用繼承?
只有當(dāng)子類真正是父類的子類型(subtype)時(shí),即當(dāng)兩者之間確實(shí)存在“is-a”關(guān)系時(shí),類B才應(yīng)該擴(kuò)展A。如果不能確定每個(gè)B確實(shí)都是A,通常情況下,B應(yīng)該包含A的一個(gè)私有實(shí)例,而且暴露一個(gè)較小的、較簡(jiǎn)單的API: A本質(zhì)上不是B的一部分,只是它的實(shí)現(xiàn)細(xì)節(jié)而已。