成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

正交設(shè)計(jì)

TwIStOy / 2912人閱讀

摘要:為了實(shí)現(xiàn)高內(nèi)聚,低耦合的軟件設(shè)計(jì),袁英杰提出了正交設(shè)計(jì)的方法論。正交設(shè)計(jì)正交是一個(gè)數(shù)學(xué)概念所謂正交,就是指兩個(gè)向量的內(nèi)積為零。鳴謝正交設(shè)計(jì)的理論原則及其方法論出自前軟件大師袁英杰先生。

Design is there to enable you to keep changing the software easily in the long term. -- Kent Beck.

設(shè)計(jì)是什么

正如Kent Beck所說(shuō),軟件設(shè)計(jì)是為了「長(zhǎng)期」更加容易地適應(yīng)未來(lái)的變化。正確的軟件設(shè)計(jì)方法是為了長(zhǎng)期地、更好更快、更容易地實(shí)現(xiàn)軟件價(jià)值的交付。

軟件設(shè)計(jì)的目標(biāo)

軟件設(shè)計(jì)就是為了完成如下目標(biāo),其可驗(yàn)證性、重要程度依次減低。

實(shí)現(xiàn)功能

易于重用

易于理解

沒(méi)有冗余

實(shí)現(xiàn)功能

實(shí)現(xiàn)功能的目標(biāo)壓倒一起,這也是軟件設(shè)計(jì)的首要標(biāo)準(zhǔn)。如何判定系統(tǒng)功能的完備性呢?通過(guò)所有測(cè)試用例。

TDD的角度看,測(cè)試用例就是對(duì)需求的闡述,是一個(gè)閉環(huán)的反饋系統(tǒng),保證其系統(tǒng)的正確性;及其保證設(shè)計(jì)的合理性,恰如其分,不多不少;當(dāng)然也是理解系統(tǒng)行為最重要的依據(jù)。

易于理解

好的設(shè)計(jì)應(yīng)該能讓其他人也能容易地理解,包括系統(tǒng)的行為,業(yè)務(wù)的規(guī)則。那么,什么樣的設(shè)計(jì)才算得上易于理解的呢?

Clean Code

Implement Patterns

Idioms

沒(méi)有冗余

沒(méi)有冗余的系統(tǒng)是最簡(jiǎn)單的系統(tǒng),恰如其分的系統(tǒng),不做任何過(guò)度設(shè)計(jì)的系統(tǒng)。

Dead Code

YAGNI: You Ain"t Gonna Need It

KISS: Keep it Simple, Stupid

易于重用

易于重用的軟件結(jié)構(gòu),使得其應(yīng)對(duì)變化更具彈性;可被容易地修改,具有更加適應(yīng)變化的能力。

最理想的情況下,所有的軟件修改都具有局部性。但現(xiàn)實(shí)并非如此,軟件設(shè)計(jì)往往需要花費(fèi)很大的精力用于依賴(lài)的管理,讓組件之間的關(guān)系變得清晰、一致、漂亮。

那么軟件設(shè)計(jì)的最高準(zhǔn)則是什么呢?「高內(nèi)聚、低耦合」原則是提高可重用性的最高原則。為了實(shí)現(xiàn)高內(nèi)聚,低耦合的軟件設(shè)計(jì),袁英杰提出了「正交設(shè)計(jì)」的方法論。

正交設(shè)計(jì)

「正交」是一個(gè)數(shù)學(xué)概念:所謂正交,就是指兩個(gè)向量的內(nèi)積為零。簡(jiǎn)單的說(shuō),就是這兩個(gè)向量是垂直的。在一個(gè)正交系統(tǒng)里,沿著一個(gè)方向的變化,其另外一個(gè)方向不會(huì)發(fā)生變化。為此,Bob大叔將「職責(zé)」定義為「變化的原因」。

「正交性」,意味著更高的內(nèi)聚,更低的耦合。為此,正交性可以用于衡量系統(tǒng)的可重用性。那么,如何保證設(shè)計(jì)的正交性呢?袁英杰提出了「正交設(shè)計(jì)的四個(gè)基本原則」,簡(jiǎn)明扼要,道破了軟件設(shè)計(jì)的精髓所在。

正交設(shè)計(jì)原則

消除重復(fù)

分離關(guān)注點(diǎn)

縮小依賴(lài)范圍

向穩(wěn)定的方向依賴(lài)

實(shí)戰(zhàn)

需求1: 存在一個(gè)學(xué)生的列表,查找一個(gè)年齡等于18歲的學(xué)生

快速實(shí)現(xiàn)
public static Student findByAge(Student[] students) {
  for (int i=0; i

上述實(shí)現(xiàn)存在很多設(shè)計(jì)的「壞味道」:

缺乏彈性參數(shù)類(lèi)型:只支持?jǐn)?shù)組類(lèi)型,List, Set都被拒之門(mén)外;

容易出錯(cuò):操作數(shù)組下標(biāo),往往引入不經(jīng)意的錯(cuò)誤;

幻數(shù):硬編碼,將算法與配置高度耦合;

返回null:再次給用戶(hù)打開(kāi)了犯錯(cuò)的大門(mén);

使用for-each

按照「最小依賴(lài)原則」,先隱藏?cái)?shù)組下標(biāo)的實(shí)現(xiàn)細(xì)節(jié),使用for-each降低錯(cuò)誤發(fā)生的可能性。

public static Student findByAge(Student[] students) {
  for (Student s : students)
    if (s.getAge() == 18)
      return s;
  return null;
}

需求2: 查找一個(gè)名字為horance的學(xué)生

重復(fù)設(shè)計(jì)

Copy-Paste是最快的實(shí)現(xiàn)方法,但會(huì)產(chǎn)生「重復(fù)設(shè)計(jì)」。

public static Student findByName(Student[] students) {
  for (Student s : students)
    if (s.getName().equals("horance"))
      return s;
  return null;
}

為了消除重復(fù),可以將「查找算法」與「比較準(zhǔn)則」這兩個(gè)「變化方向」進(jìn)行分離。

抽象準(zhǔn)則

首先將比較的準(zhǔn)則進(jìn)行抽象化,讓其獨(dú)立變化。

public interface StudentPredicate {
  boolean test(Student s);
}

將各個(gè)「變化原因」對(duì)象化,為此建立了兩個(gè)簡(jiǎn)單的算子。

public class AgePredicate implements StudentPredicate {
  private int age;
  
  public AgePredicate(int age) {
    this.age = age;
  }
  
  @Override
  public boolean test(Student s) {
    return s.getAge() == age;
  }
}
public class NamePredicate implements StudentPredicate {
  private String name;
  
  public NamePredicate(String name) {
    this.name = name;
  }
  
  @Override
  public boolean test(Student s) {
    return s.getName().equals(name);
  }
}

此刻,查找算法的方法名也應(yīng)該被「重命名」,使其保持在同一個(gè)「抽象層次」上。

public static Student find(Student[] students, StudentPredicate p) {
  for (Student s : students)
    if (p.test(s))
      return s;
  return null;
}

客戶(hù)端的調(diào)用根據(jù)場(chǎng)景,提供算法的配置。

assertThat(find(students, new AgePredicate(18)), notNullValue());
assertThat(find(students, new NamePredicate("horance")), notNullValue());
結(jié)構(gòu)性重復(fù)

AgePredicateNamePredicate存在「結(jié)構(gòu)型重復(fù)」,需要進(jìn)一步消除重復(fù)。經(jīng)分析兩個(gè)類(lèi)的存在無(wú)非是為了實(shí)現(xiàn)「閉包」的能力,可以使用lambda表達(dá)式,「Code As Data」,簡(jiǎn)明扼要。

assertThat(find(students, s -> s.getAge() == 18), notNullValue());
assertThat(find(students, s -> s.getName().equals("horance")), notNullValue());
引入Iterable

按照「向穩(wěn)定的方向依賴(lài)」的原則,為了適應(yīng)諸如List, Set等多種數(shù)據(jù)結(jié)構(gòu),甚至包括原生的數(shù)組類(lèi)型,可以將入?yún)⒅貥?gòu)為重構(gòu)為更加抽象的Iterable類(lèi)型。

public static Student find(Iterable students, StudentPredicate p) {
  for (Student s : students)
    if (p.test(s))
      return s;
  return null;
}

需求3: 存在一個(gè)老師列表,查找第一個(gè)女老師

類(lèi)型重復(fù)

按照既有的代碼結(jié)構(gòu),可以通過(guò)Copy Paste快速地實(shí)現(xiàn)這個(gè)功能。

public interface TeacherPredicate {
  boolean test(Teacher t);
}
public static Teacher find(Iterable teachers, TeacherPredicate p) {
  for (Teacher t : teachers)
    if (p.test(t))
      return t;
  return null;
}

用戶(hù)接口依然可以使用Lambda表達(dá)式。

assertThat(find(teachers, t -> t.female()), notNullValue());

如果使用Method Reference,可以進(jìn)一步地改善表達(dá)力。

assertThat(find(teachers, Teacher::female), notNullValue());
類(lèi)型參數(shù)化

分析StudentMacher/TeacherPredicate, find(Iterable)/find(Iterable)的重復(fù),為此引入「類(lèi)型參數(shù)化」的設(shè)計(jì)。

首先消除StudentPredicateTeacherPredicate的重復(fù)設(shè)計(jì)。

public interface Predicate {
  boolean test(E e);
}

再對(duì)find進(jìn)行類(lèi)型參數(shù)化設(shè)計(jì)。

public static  E find(Iterable c, Predicate p) {
  for (E e : c)
    if (p.test(e))
      return e;
  return null;
}
型變

find的類(lèi)型參數(shù)缺乏「型變」的能力,為此引入「型變」能力的支持,接口更加具有可復(fù)用性。

public static  E find(Iterable c, Predicate p) {
  for (E e : c)
    if (p.test(e))
      return e;
  return null;
}
復(fù)用lambda

Parameterize all the things.

觀察如下兩個(gè)測(cè)試用例,如果做到極致,可認(rèn)為兩個(gè)lambda表達(dá)式也是重復(fù)的。從「分離變化的方向」的角度分析,此lambda表達(dá)式承載的「比較算法」與「參數(shù)配置」兩個(gè)職責(zé),應(yīng)該對(duì)其進(jìn)行分離。

assertThat(find(students, s -> s.getName().equals("Horance")), notNullValue());
assertThat(find(students, s -> s.getName().equals("Tomas")), notNullValue());

可以通過(guò)「Static Factory Method」生產(chǎn)lambda表達(dá)式,將比較算法封裝起來(lái);而配置參數(shù)通過(guò)引入「參數(shù)化」設(shè)計(jì),將「邏輯」與「配置」分離,從而達(dá)到最大化的代碼復(fù)用。

public final class StudentPredicates {
  private StudentPredicates() {
  }

  public static Predicate age(int age) {
    return s -> s.getAge() == age;
  } 
  
  public static Predicate name(String name) {
    return s -> s.getName().equals(name);
  }
}
import static StudentPredicates.*;

assertThat(find(students, name("horance")), notNullValue());
assertThat(find(students, age(10)), notNullValue());
組合查詢(xún)

但是,上述將lambda表達(dá)式封裝在Factory的設(shè)計(jì)是及其脆弱的。例如,增加如下的需求:

需求4: 查找年齡不等于18歲的女生

最簡(jiǎn)單的方法就是往StudentPredicates不停地增加「Static Factory Method」,但這樣的設(shè)計(jì)嚴(yán)重違反了「OCP」(開(kāi)放封閉)原則。

public final class StudentPredicates {
  ......

  public static Predicate ageEq(int age) {
    return s -> s.getAge() == age;
  } 
  
  public static Predicate ageNe(int age) {
    return s -> s.getAge() != age;
  } 
}

從需求看,比較準(zhǔn)則增加了眾多的語(yǔ)義,再次運(yùn)用「分離變化方向」的原則,可發(fā)現(xiàn)存在兩類(lèi)運(yùn)算的規(guī)則:

比較運(yùn)算:==, !=

邏輯運(yùn)算:&&, ||

比較語(yǔ)義

先處理比較運(yùn)算的變化方向,為此建立一個(gè)Matcher的抽象:

public interface Matcher {
  boolean matches(T actual);
    
  static  Matcher eq(T expected) {
    return actual -> expected.equals(actual);
  }
  
  static  Matcher ne(T expected) {
    return actual -> !expected.equals(actual);
  }
}

Composition everywhere.

此刻,age的設(shè)計(jì)運(yùn)用了「函數(shù)式」的思維,其行為表現(xiàn)為「高階函數(shù)」的特性,通過(guò)函數(shù)的「組合式設(shè)計(jì)」完成功能的自由拼裝組合,簡(jiǎn)單、直接、漂亮。

public final class StudentPredicates {
  ......

  public static Predicate age(Matcher m) {
    return s -> m.matches(s.getAge());
  }
}

查找年齡不等于18歲的學(xué)生,可以如此描述。

assertThat(find(students, age(ne(18))), notNullValue());
邏輯語(yǔ)義

為了使得邏輯「謂詞」變得更加人性化,可以引入「流式接口」的「DSL」設(shè)計(jì),增強(qiáng)表達(dá)力。

public interface Predicate {
  boolean test(E e);

  default Predicate and(Predicate other) {
    return e -> test(e) && other.test(e);
  }
}

查找年齡不等于18歲的女生,可以表述為:

assertThat(find(students, age(ne(18)).and(Student::female)), notNullValue());
重復(fù)再現(xiàn)

仔細(xì)的讀者可能已經(jīng)發(fā)現(xiàn)了,StudentTeacher兩個(gè)類(lèi)也存在「結(jié)構(gòu)型重復(fù)」的問(wèn)題。

public class Student {
  public Student(String name, int age, boolean male) {
    this.name = name;
    this.age = age;
    this.male = male;
  }
  
  ......
  
  private String name;
  private int age;
  private boolean male;
}
public class Teacher {
  public Teacher(String name, int age, boolean male) {
    this.name = name;
    this.age = age;
    this.male = male;
  }
  
  ......
  
  private String name;
  private int age;
  private boolean male;
}
級(jí)聯(lián)反應(yīng)

StudentTeacher的結(jié)構(gòu)性重復(fù),導(dǎo)致StudentPredicatesTeacherPredicates也存在「結(jié)構(gòu)性重復(fù)」。

public final class StudentPredicates {
  ......

  public static Predicate age(Matcher m) {
    return s -> m.matches(s.getAge());
  }
}
public final class TeacherPredicates {
  ......

  public static Predicate age(Matcher m) {
    return t -> m.matches(t.getAge());
  }
}

為此需要進(jìn)一步消除重復(fù)。

提取基類(lèi)

第一個(gè)直覺(jué),通過(guò)「提取基類(lèi)」的重構(gòu)方法,消除StudentTeacher的重復(fù)設(shè)計(jì)。

class Human {
  protected Human(String name, int age, boolean male) {
    this.name = name;
    this.age = age;
    this.male = male;
  }
    
  ...
  
  private String name;
  private int age;
  private boolean male;
}

從而實(shí)現(xiàn)了進(jìn)一步消除了StudentTeacher之間的重復(fù)設(shè)計(jì)。

public class Student extends Human {
  public Student(String name, int age, boolean male) {
    super(name, age, male);
  }
}

public class Teacher extends Human {
  public Teacher(String name, int age, boolean male) {
    super(name, age, male);
  }
}
類(lèi)型界定

此時(shí),可以通過(guò)引入「類(lèi)型界定」的泛型設(shè)計(jì),使得StudentPredicatesTeacherPredicates合二為一,進(jìn)一步消除重復(fù)設(shè)計(jì)。

public final class HumanPredicates {
  ......
  
  public static  
    Predicate age(Matcher m) {
    return s -> m.matches(s.getAge());
  } 
}
消滅繼承關(guān)系

StudentTeacher依然存在「結(jié)構(gòu)型重復(fù)」的問(wèn)題,可以通過(guò)Static Factory Method的設(shè)計(jì)方法,并讓Human的構(gòu)造函數(shù)「私有化」,刪除StudentTeacher兩個(gè)子類(lèi),徹底消除兩者之間的「重復(fù)設(shè)計(jì)」。

public class Human {
  private Human(String name, int age, boolean male) {
    this.name = name;
    this.age = age;
    this.male = male;
  }
  
  public static Human student(String name, int age, boolean male) {
    return new Human(name, age, male);
  }
  
  public static Human teacher(String name, int age, boolean male) {
    return new Human(name, age, male);
  }
  
  ......
}
消滅類(lèi)型界定

Human的重構(gòu),使得HumanPredicates的「類(lèi)型界定」變得多余,從而進(jìn)一步簡(jiǎn)化了設(shè)計(jì)。

public final class HumanPredicates {
  ......
  
  public static Predicate age(Matcher m) {
    return s -> m.matches(s.getAge());
  } 
}
絕不返回null

Billion-Dollar Mistake

在最開(kāi)始,我們遺留了一個(gè)問(wèn)題:find返回了null。用戶(hù)調(diào)用返回null的接口時(shí),常常忘記null的檢查,導(dǎo)致在運(yùn)行時(shí)發(fā)生NullPointerException異常。

按照「向穩(wěn)定的方向依賴(lài)」的原則,find的返回值應(yīng)該設(shè)計(jì)為Optional,使用「類(lèi)型系統(tǒng)」的特長(zhǎng),取得如下方面的優(yōu)勢(shì):

顯式地表達(dá)了不存在的語(yǔ)義;

編譯時(shí)保證錯(cuò)誤的發(fā)生;

import java.util.Optional;

public  Optional find(Iterable c, Predicate p) {
  for (E e : c) {
    if (p.test(e)) {
      return Optional.of(e);
    }
  }
  return Optional.empty();
}
回顧

通過(guò)4個(gè)需求的迭代和演進(jìn),通過(guò)運(yùn)用「正交設(shè)計(jì)」和「組合式設(shè)計(jì)」的基本思想,加深對(duì)「正交設(shè)計(jì)基本原則」的理解。

鳴謝

「正交設(shè)計(jì)」的理論、原則、及其方法論出自前ThoughtWorks軟件大師「袁英杰」先生。英杰既是我的老師,也是我的摯友;他高深莫測(cè)的軟件設(shè)計(jì)的修為,及其對(duì)軟件設(shè)計(jì)獨(dú)特的哲學(xué)思維方式,是我等后輩學(xué)習(xí)的楷模。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/65584.html

相關(guān)文章

  • 軟件測(cè)試肖sir__005測(cè)試用例設(shè)計(jì)方法(1)

    摘要:需要結(jié)合其他測(cè)試用例設(shè)計(jì)的方法進(jìn)行補(bǔ)充。比如邊界值邊界值在軟件中邊界值測(cè)試方法是發(fā)現(xiàn)錯(cuò)誤能力最強(qiáng)的一種。其中,原因是表示輸入條件,結(jié)果是對(duì)輸入執(zhí)行的一系列計(jì)算后得到的輸出。與取值或,表示某狀態(tài)不出現(xiàn),則表示某狀態(tài)出現(xiàn)。 ...

    gnehc 評(píng)論0 收藏0
  • 常見(jiàn)的測(cè)試用例設(shè)計(jì)方法

    摘要:因果圖分析法是一種圖解法分析輸入的各種組合情況,從而設(shè)計(jì)測(cè)試用例的方法。工具錯(cuò)誤推測(cè)法根據(jù)實(shí)際經(jīng)驗(yàn)或推測(cè)分析列出所有可能存在的和容易發(fā)生錯(cuò)誤的情況,并有針對(duì)性的設(shè)計(jì)測(cè)試用例。 1.等價(jià)類(lèi)劃分 等價(jià)類(lèi)是指某個(gè)輸入域的子集合.在該子集合中,各個(gè)輸入數(shù)據(jù)對(duì)于揭露程序中的錯(cuò)誤都是等效的.并合理地假定:測(cè)試某等價(jià)類(lèi)的代表值就等于對(duì)這一類(lèi)其它值的測(cè)試。 把輸入數(shù)據(jù)合理地劃分等價(jià)類(lèi),在每一個(gè)等價(jià)類(lèi)中...

    toddmark 評(píng)論0 收藏0
  • <<深入PHP面向?qū)ο?、模式與實(shí)踐>>讀書(shū)筆記:面向?qū)ο?em>設(shè)計(jì)和過(guò)程式編程

    摘要:注本文內(nèi)容來(lái)深入面向?qū)ο竽J脚c實(shí)踐中節(jié)。面向?qū)ο笤O(shè)計(jì)與過(guò)程式編程面向?qū)ο笤O(shè)計(jì)和過(guò)程式編程有什么不同呢可能有些人認(rèn)為最大的不同在于面向?qū)ο缶幊讨邪瑢?duì)象。面向?qū)ο缶幊毯瓦^(guò)程式編程的一個(gè)核心區(qū)別是如何分配職責(zé)。 注:本文內(nèi)容來(lái)中6.2節(jié)。 6.2 面向?qū)ο笤O(shè)計(jì)與過(guò)程式編程 ??面向?qū)ο笤O(shè)計(jì)和過(guò)程式編程有什么不同呢?可能有些人認(rèn)為最大的不同在于面向?qū)ο缶幊讨邪瑢?duì)象。事實(shí)上,這種說(shuō)法不準(zhǔn)確。...

    xiao7cn 評(píng)論0 收藏0
  • 馬蜂窩ABTest多層分流系統(tǒng)的設(shè)計(jì)與實(shí)現(xiàn)

    摘要:為了解決以上問(wèn)題,我們的分流系統(tǒng)選擇基于實(shí)現(xiàn),通過(guò)或者協(xié)議來(lái)傳遞分流信息。正交是指用戶(hù)進(jìn)入所有的實(shí)驗(yàn)之間沒(méi)有必然關(guān)系。流量層內(nèi)實(shí)驗(yàn)分流流量層內(nèi)實(shí)驗(yàn)的因子有設(shè)備流量層。統(tǒng)計(jì)功效對(duì)于置信區(qū)間特征值等產(chǎn)品化功能支持。 什么是 ABTest 產(chǎn)品的改變不是由我們隨便「拍腦袋」得出,而是需要由實(shí)際的數(shù)據(jù)驅(qū)動(dòng),讓用戶(hù)的反饋來(lái)指導(dǎo)我們?nèi)绾胃玫馗纳品?wù)。正如馬蜂窩 CEO 陳罡在接受專(zhuān)訪(fǎng)時(shí)所說(shuō):「有...

    mingzhong 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<