Chapter5-面向可複用的軟件構造方法

目錄

Chapter 5:Reusability-Oriented Software Construction Approaches
本章面向一個重要的外部質量指標:可複用性——如何構造出可在不同應用中重複使用的軟件模塊/API?

5.1 Metric, Morphology and External Observations of Reusability

本節探討可複用軟件的形態與特徵
考點:
Programming for/with reuse
白盒、黑盒框架的原理和實現

5.1.1 About Reuse

可複用性:不針對應用,而是針對多個引用場景。

Software reuse

Two perspectives of software reuse
programming for reuse(面向複用編程,開發出可複用的軟件->抽取共性,識別差異,設計繼承樹) programming with reuse(基於複用的編程,利用已有的可複用的軟件搭建應用系統->搜索,選擇適配)
開發成本高於一般軟件的成本:要有足夠高的適應性;性能差些:針對更普適場景,缺少足夠的針對性 可複用軟件庫,對其進行有效的管理;往往無法拿來就用,需要適配

Why reuse?

Why reuse?
Reuse is **cost-effective** and with **timeliness**(降低成本和開發時間) Reuse produces **reliable** software(經過充分測試,**可靠、穩定**) Reuse yields **standardization**(**標準化**,在不同應用中保持一致)

Measure resuability
複用的機會有多頻繁?
複用的場合有多少?
複用的代價有多大?(搜索、獲取;適配,擴展;實例化;與軟件其他部分的互連)

A software asset with high reusability should:
– Brief (small size) and Simple (low complexity) 小、簡單
– Portable and Standard Compliance 與標準兼容
– Adaptable and Flexible 靈活可變
– Extensibility 可擴展
– Generic and Parameterization 泛型、參數化
– Modularity 模塊化
– Localization of volatile (changeable) design assumptions 變化的局部性
– Stability under changing requirements 穩定
– Rich documentation 豐富的文檔和幫助

5.1.2 Levels and morphology of reusable components

Type of Code Reuse(代碼複用的類型)

White box reuse(白盒複用) Black box reuse(黑盒複用)
源代碼可見,可修改和擴展(複製已有代碼當正在開發的系統,進行修改,可定製化程度高,對其修改增加了軟件的複雜度,且需要對其內部充分的瞭解) 源代碼不可見,不能修改(只能通過API接口來使用,無法修改代碼,簡單、清晰,適應性差)

Reuse Level(複用級別)

Source code level reuse(源代碼級別複用) Module-level reuse(模塊級別複用) Library-level(庫函數級別) System-level reuse(系統級別)
methods, statements class/abstract class/interface API/package(.jar) framework(框架)
Copy/paste;Exampel code search: ①grepcode.com ②github.com/search ③searchcode.com Approaches of reusing a class: inheritance(繼承, override), delegation(委託,一個對象將某些功能傳遞給另一個對象) Libraries: A set of classes and methods(APIs) that provide reusable functionality.開發者構造可運行軟件實體,其中涉及到對可複用庫的調用 框架:一組具體類、抽象類、及其之間的連接關係;開發者根據framework的規約,填充自己的代碼進去,形成完整系統;**Framework作爲主程序**加以執行,執行過程調用開發者所寫的程序

Details For Frameworks(關於框架)

framework
Whitebox frameworks(白盒框架) Blackbox frameworks(黑盒框架)
通過繼承(inheritance)和動態綁定(dynamic binding)實現的可擴展性;通過繼承(subclassing)框架基類並重寫(overriding)預定義的hook(鉤子)方法來擴展現有功能;通常使用諸如模板方法模式的設計模式來覆蓋鉤子方法 通過爲可插入框架的組件定義接口來實現可擴展性; 通過定義符合特定接口的組件來重用現有功能; 這些組件通過**委託**(delegation)與框架集成
通過**subclassing**(繼承)、**override**實現定製功能;允許擴展每個非私有方法;需要了解超類的實現;每次只有一個擴展;一起編譯;通常稱爲開發框架 通過**plugin interface**(**delegation**、**composition**)實現;允許擴展在接口中顯示的功能;只需要瞭解界面;多個插件;通常提供更多的模塊化;單獨部署稱爲可能(.jar,.dll,…);通常所謂的最終用戶框架、平臺
程序執行的主體是開發者實現的**子類** 程序執行的主體是**framework**
Domain Engineering(領域工程):①定義域(domain)②分解和實施通用部件爲框架③爲可變部分提供插件接口和回調機制(callback)④獲取大量反饋,並進行迭代
運行框架:①通過自身運行(eg. Eclipse)②通過繼承運行(eg. Swing,JUnit,MapReduce,Servlets)③加載插件(Client寫main函數,創造插件並傳遞給框架(白盒);框架寫main函數,client將插件名字作爲命令或環境變量傳遞(黑盒))
5.1.3 External observations of reusability
Type Variation(類型可變) Routine Grouping(功能分組) Implementation Variation(實現可變) Representation Independence(表示獨立) Factoring Out Common Behaviors(共性抽取)
泛型:適應不同的類型,且滿足LSP 提供完備的細粒度操作,保證功能的完整性,不同場景下複用不同的操作及其組合 ADT有多種不同的實現,提供不同的representation和abstract function,但具有同樣的specification(pre-condition,post-condition,invariants),從而可以適應不同的應用場景 內部實現可能會經常變化,但客戶端不應受到影響,表示獨立性、信息隱藏 將共同的行爲(共性)抽象出來,形成可複用實體

5.2 Construction for Reuse

從類、API、框架三個層面學習如何設計可複用軟件實體的具體技術
本節考點:
LSP
協變、反協變
數組的子類型化
泛型的子類型化
泛型中的通配符(?)
Delegation
Comparator和Comparable
CRP原則
接口的組合

5.2.1 Designing reusable classes

設計可複用的類
Already introduced in section 2.4 OOP

5.2.1.1 Inheritance and overriding(繼承和重寫)
5.2.1.2 Overloading(重載)
5.2.1.3 Parametric polymorphism and generic programming(參數多態與泛型編程)

Important in this chapter

5.2.1.4 Behavioral subtyping and Liskov Substitution Priciple(LSP)(行爲子類型與Liskov替換原則)

子類型多態:客戶端可用統一的方式處理不同類型的對象(static type checking)

/* Cat extends Animal */
Animal a = new Animal();
Animal c1 = new Cat();
Cat c2 = new Cat();
/* 在可以使用a的場景,都可以用c1和c2代替而不會有任何問題 */
a = c1;
a = c2;

子類型在Java編譯器中執行的規則:
①子類型可以增加方法,但不可刪
②子類型需要實現抽象類型中的所有未實現方法
③子類型中重寫的方法必須有相同或子類型的返回值
④子類型中重寫的方法必須使用同樣類型的參數
⑤子類型中重寫的方法不能拋出額外的異常
特殊的行爲:
更強的不變量
更弱的前置條件
更強的後置條件

Liskov Substitution Priciple(LSP): Let q(x) be a property provable about objects x of type T, then q(y) should be provable for objects y of type S where S is a subtype of T.
(對於T類型的對象x有q(x)成立,且S是T的子類型,則對於S類型的對象y有q(y)成立)

LSP是子類型關係的一個特殊定義,稱爲強行爲子類型
在編程語言中遵循以下規則:
①前置條件不能強化
②後置條件不能弱化
③不變量要保持
④子類型方法參數:逆變(反協變,越來越抽象)
⑤子類型方法的返回值:協變(越來越具體)
⑥異常類型:協變(越來越具體)

Covariance(協變) Contravariance(反協變、逆變) 兩種特別的LSP Wildcards(通配符)
父類型->子類型:越來越具體specification; 返回值類型:不變或變得更具體; 異常的類型:也是如此 父類型->子類型:越來越具體specific; 參數類型:要相反的變化,要不變或越來越抽象 數組中的LSP 泛型中的LSP 解決泛型中的LSP問題
Example: ①返回值類型,Object a(){…} -> String a(){…} ②異常類型,throws Throwable -> throws IOException Example: ①參數類型,void c(String s){…} -> void c(Object s){…} Arrays are covariant.(定義抽象類型可以放入具體類型,父類型可以放入子類型) ArrayList<Sting> 是List< Sting> 子類型; List<Sting> 不是 List<Object> 子類型; 這個過程稱爲類型擦除(泛型不是協變的) List<? extends T> (Upper Bounded Wildcards,T的子類型) ; List<Number> 是 List<?> 子類型; List<Number> 是 List<? extends Object> 的子類型(Number是Object/Number等子類的子類型型); List<Object> 是 List<? super String/Number/Object> 的子類型(Object是String/Object等任意類型父類的子類型);

關於上述問題代碼分析:
①子類型中參數類型協變;需要注意,如下情況不是子類型,因此是正確的

    void someMethod(Number n){...};
    someMethod(new  Integer(10);
    someMethod(new Double(10.1);

②數組的LSP,需要注意子類型對象賦值和子類型引用賦值,前者正確,後者錯誤

/* T[]能保存T及T的子類型對象(子類型對象賦值)*/
        Number[] numbers = new Number[2];
        numbers[0] = new Integer(10);
        numbers[1] = new Double(3.14);
/* 當將一個子類型數組賦值給父類型數組後,父類型數組只能保存該子類型元素,因爲是子類型引用賦值*/
        Integer[] myInts = {1, 2, 3, 4};
        Number[] myNumbers = myInts;
        myNumber[0] = 3.14;  /* 報錯java.lang.ArrayStoreException */

③泛型中的LSP,泛型不是協變的

/* List<Integer>不是List<Number>的子類型,即使IntegerNumber的子類型 */
    List<Integer> myInts = new ArrayList<Integer>();
    myInts.add(1);
    myInts.add(2);
    List<Number> myNums = myInts; /* Type mismatch: cannot convert from List<Integer> to List<Number> */
    myNums.add(3.14);
/* 作爲方法參數時,也是錯誤的 */
static long sum(List<Number> numbers) {...}
    List<Integer> myInts = asList(1,2,3,4,5);
    List<Long> myLongs = asList(1L, 2L, 3L, 4L, 5L);
    List<Double> myDoubles = asList(1.0, 2.0, 3.0, 4.0, 5.0);
    /* The method sum(List<Number>) is not applicable for the arguments (List<Integer/Long/Double>) */
    sum(myInts);
    sum(myLongs);
    sum(myDoubles);

④通配符的應用,使用通配符的泛型的LSP

    /* Success */
    List<? extends Integer> intList = new ArrayList<>();
    List<? extends Number> numList = intList;
    /* failure,第一個爲Number的子類型,第二個爲Integer的子類型*/
    List<? extends Number> intList = new ArrayList<>();
    List<? extends Integer> numList = intList;
    /* 根據上述通配符的敘述,以下正確,其中T爲任意類型 */
    public static <T> void copy(List<? super T> dest,List<? extends T> src){...};
    List<Number> source = new LinkedList<>(); /* Number類型是T類型子類的子類型 */
    source.add(Float.valueOf(3));
    source.add(Integer.valueOf(2));
    source.add(Double.valueOf(1.1));
    List<Object> dest = new LinkedList<>(); /* Object是T類型父類的子類型 */
    Collections.copy(dest,source);
5.2.1.5 Composition and delegation(組合和委託)

Delegation & CRP原則

Delegation(委託/委派) Composition Reuse Principle(CRP,複合重用原則,Or Composite over inheritance Principle)
一個對象請求另一個對象的功能(Client調用Recevier,Recevier委派給Delegate;委派是複用的一種常見形式);如果子類只需要複用父類中的一小部分方法,可以不需要使用繼承,而是通過委派機制來實現 類應該通過它們的組合(通過包含實現所需功能的其他類的實例)實現多態行爲和代碼重用,而不是從基類或父類繼承;優先使用對象組合,而不是類繼承
establish delegation between two objects; A a = new A(); B b = new B(a)(Class B內部存在一個delegation link -> private A a,如果B要使用A的foo()功能,則可以直接調用a.foo()實現) 使用接口定義不同側面的行爲;接口之間通過extends(繼承)實現行爲的擴展(一個接口可以繼承多個接口- > 組合);類implements(實現)接口(類的內部使用委派)

Types of delegation(三種委派方式)

Types of delegation
Use(A use B) Association(A has B) Composition / aggregation(A owns N)
Dependency(依賴,臨時性的delegation) Association(關聯,永久性的delegation) Composition(組合,更強的delegation) Aggregation(聚合)
直接調用其他對象的方法(uses_a) 這種關係是結構性的,因爲它指定一種類型的對象與另一種類型的對象相連,並不代表行爲(has_a) 將簡單對象或數據類型組合成更復雜的對象;該類有另一個作爲屬性/實例變量(內部創建實例對象,is_part_of) 對象存在於另一個之外,在外部創建,所以它作爲參數傳遞給構造者(has_a)

Delegation示例
Client調用Printer的print方法
Printer將print委託給RealPrinter

class RealPrinter {
    void print() {
        System.out.println("The Delegate");
    }
}
class Printer {
    RealPrinter p = new RealPrinter();  /* 委託連接 */
    void print() {
        p.print(); /* 委託 */
    }
}
    Printer printer = new Printer();
    printer.print(); /* The delegation */

Composition示例

class Heart {}
class Person {
    /* 內部創建實例變量 */
    private Heart heart = new Heart();
    public void operation () {
        /* 也可以在方法內創建實例 */
        // heart = new Heart();
        heart.operation();
    }
}

Aggregation(聚合)示例
對象存在於另一個之外,在外部創建,所以它作爲參數傳遞給構造者

class Student {}
class Course {
    private Student[] students;
    /* 外部參數傳入 */
    public addStudent (Student s) {
        studtents.append(s);
    }
}
5.2.2 Designing system-level reusable libraries and frameworks(系統層面複用)

之所以libraryframework被稱爲系統層面的複用,是因爲它們不僅定義了1個可複用的接口/類,而是將某個完整系統中的所有可複用的接口/類都實現出來。並且定義了這些類之間的交互關係調用關係,從而形成了系統整體的“架構”

5.2.2.1 API design

(1) API should do one thing and do it well(努力做好一件事情;Good. Font,PrivateKey,ThreadFactory;Bad. BindingIteratorImplBase)
(2) API should be as small as possible but no smaller(越小越好)
(3) Implementation should not impact API(信息隱藏,用戶不應該影響API)
(4) Documentation matters(文檔遵循Chapter4的可讀性)
(5) Consider performance consequences(性能良好)
(6) API must coexist peacefully with platform(友好性)
(7) Class design(最小化可變性;繼承遵循LSP)
(8) Method design(編譯時最好 - 靜態類型、泛型)

5.2.2.2 Framework design

Turn to 5.1.2

5.2.2.3 Java Collection Framework(JCF)

Collection: an object that groups element

General Purpose Implementations
Interface -> Abstract Class -> Class
Collection Interface + Iterator Interface

Implementations
Hash Table Resizable Array Balanced Tree Linked List
Interfaces Set HashSet(O(1) access, no order guarantee) TreeSet(O(log n) access, sorted)
List ArrayList(O(1) random access, O(n) insert/remove) LinkedList(O(n) random access, O(1) insert/remove)
Map HashMap TreeMap

Idioms(常用方法)

/**** Set ****/
    Set<Type> s1, s2;
    boolean isSubset = s1.containsAll(s2); /* 子集合包含 */
    Set<Type> union = new HashSet<>(s1);
    union = union.addAll(s2); /* 集合交集 */
    Set<Type> intersection = new HashSet<>(s1);
    intersection.retainAll(s2); /* 集合並集 */
    Set<Type> difference = new HashSet<>(s1);
    difference.removeAll(s2); /* 集合差集 */
    Collection<Type> c; 
    Collection<Type> noDups = new HashSet<>(c); /* 子類型 */
/**** List可交換和隨機化的可重用算法 ****/
    List<Type> a, b;
    a.addAll(b); //連接兩個list
    a.subList(from, to).clear(); //Range-remove,刪除該範圍內所有元素
    // Range-extract,提取該範圍內元素
    List<Type> partView = a.subList(from, to);
    List<Type> part = new ArrayList<>(partView);
    partView.clear();
/**** Map ****/
    // Iterate over all keys in Map m,迭代器遍歷所有鍵值
    Map<Key, Val> m;
    for (iterator<Key> i = m.keySet().iterator(); i.hasNext(); )
        System.out.println(i.next());
    // As of Java 5 (2004),遍歷
    for (Key k : m.keySet())
        System.out.println(i.next());
    // "Map algebra"
    Map<Key, Val> a, b;
    boolean isSubMap = a.entrySet().containsAll(b.entrySet()); /* a鍵值集合是否包含b鍵值集合 */
    Set<Key> commonKeys =new HashSet<>(a.keySet()).retainAll(b.keySet); /* 並集 */
    //Remove keys from a that have mappings in b
    a.keySet().removeAll(b.keySet());

Unmodifiable Wrappers(不可更改包裝):匿名實現;靜態工廠方法;每個核心接口一個;提供只讀訪問 - 不可變!
Synchronization Wrappers(同步包裝):線程安全的新方法 - 匿名實現,每個核心接口一個 - 靜態工廠收集適當的類型 - 如果通過包裝器進行所有訪問,線程安全保證 - 必須手動同步迭代

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章