《重構-代碼整潔之道TypeScript版》第4天


今天讓咱們來繼續第4天,老規矩先來回顧一下昨天咱們都實現了哪些:前端

  • Consolidate Conditional Expression(合併條件表達式) 
  • Consolidate Duplicate Conditional Fragments(合併重複的條件片斷)  
  • Convert Procedural Design to Objects(將過程化設計轉化爲對象設計)  

(圖片:梅里雪山)java

什麼是重構 ?

簡單理解就是不改變軟件可觀察行爲的前提下,改善其內部結構,以提升理解性和下降修改爲本。node


1. 這是以下咱們要實現的目標任務列表(天天進步一點點⏰)  

  • Decompose Conditional(分解條件表達式) 
  • Duplicate Observed Data(複製「被監視數據」)  
  • Encapsulate Collection(封裝集合)  
  • Encapsulate Downcast(封裝向下轉型)  
  • Encapsulate Field(封裝字段) 
  • Extract Class(提煉類) 
  • Extract Hierarchy(提煉繼承體系) 
  • Extract Interface(提煉接口)  
  • Extract Method(提煉函數)  
  • Extract Subclass(提煉子類) 
  • Extract Superclass(提煉超類)  
  • Form Template Method(塑造模板函數)  
  • Hide Delegate(隱藏「委託關係」)  
  • Hide Method(隱藏函數)  
  • Inline Class(將類內聯化)  
  • Inline Method(內聯函數)  
  • Inline Temp(內聯臨時變量) 
  • Introduce Assertion(引入斷言)  
  • Introduce Explaining Variable(引入解釋性變量)  
  • Introduce Foreign Method(引入外加函數) 
  • Introduce Local Extension(引入本地擴展)  
  • Introduce Null Object(引入Null對象) 
  • Introduce Parameter Object(引入參數對象)  
  • Move Field(搬移字段)  
  • Move Method(搬移函數) 
  • Parameterize Method(令函數攜帶參數)  
  • Preserve Whole Object(保持對象完整) 
  • Pull Up Constructor Body(構造函數本體上移) 
  • Pull Up Field(字段上移) 
  • Pull Up Method(函數上移)  
  • Push Down Field(字段下移)  
  • Push Down Method(函數下移)  
  • Remove Assignments to Parameters(移除對參數的賦值)  
  • Remove Control Flag(移除控制標記) 
  • Remove Middle Man(移除中間人) 
  • Remove Parameter(移除參數)  
  • Remove Setting Method(移除設值函數) 
  • Rename Method(函數更名)  
  • Replace Array with Object(以對象取代數組)  
  • Replace Conditional with Polymorphism(以多態取代條件表達式) 
  • Replace Constructor with Factory Method(以工廠函數取代構造函數)  
  • Replace Data Value with Object(以對象取代數據值)  
  • Replace Delegation with Inheritance(以繼承取代委託) 
  • Replace Error Code with Exception(以異常取代錯誤碼)  
  • Replace Exception with Test(以測試取代異常)  
  • Replace Inheritance with Delegation(以委託取代繼承) 
  • Replace Magic Number with Symbolic Constant(以字面常量取代魔法數)  
  • Replace Method with Method Object(以函數對象取代函數) 
  • Replace Nested Conditional with Guard Clauses(以衛語句取代嵌套條件表達式)  
  • Replace Parameter with Explicit Methods(以明確函數取代參數)  
  • Replace Parameter with Methods(以函數取代參數) 
  • Replace Record with Data Class(以數據類取代記錄)  
  • Replace Subclass with Fields(以字段取代子類) 
  • Replace Temp with Query(以查詢取代臨時變量)  
  • Replace Type Code with Class(以類取代類型碼)  
  • Replace Type Code with State/Strategy(以State/Strategy取代類型碼)  
  • Replace Type Code with Subclasses(以子類取代類型碼) 
  • Self Encapsulate Field(自封裝字段)  
  • Separate Domain from Presentation(將領域和表述/顯示分離) 
  • Separate Query from Modifier(將查詢函數和修改函數分離) 
  • Split Temporary Variable(分解臨時變量)  
  • Substitute Algorithm(替換算法)  
  • Tease Apart Inheritance(梳理並分解繼承體系)  

2. Decompose Conditional(分解條件表達式) 

描述🍏:你有一個複雜的if..else if ...else語句,能夠把它從複雜的代碼中提取出來面試

動機🍃: 在業務開發中,你必須編寫代碼來檢查不一樣的條件分支、根據不一樣的分支作不一樣的事,而後你就會獲得一個至關長的函數,大型函數自身就會使代碼的可讀性降低,而條件邏輯則會使代碼更難閱讀。算法

//假設我要計算購買某樣商品的總價(總價=數量X單價),而這個商品在冬季和夏季的單價是不一樣的:
if (date.before(SUMMER_START) || date.after(SUMMER_END)) {
  charge = quantity * _winterRate + _winterServiceCharge;
} else {
  charge = quantity * _summerRate;
}
複製代碼

我把每一個分支的判斷條件都提煉到一個獨立函數中,以下所示(僞代碼):typescript

if(Summer(date)){
  charge = winterCharge(quantity);
}else{
  charge = summerCharge(quantity);
} 
// ...以下代碼在類中
private  Summer(date:Date):boolean{
  return date.before(SUMMER_START) || date.after(SUMMER_END); 
}
private  summerCharge(quantity:number):number{
  return quantity*summerRate;
}
private  winterCharge(int quantity){
  return quantity* winterRate+_winterServiceCharge;
}
複製代碼

經過如上代碼咱們能看到整個結構更加清晰。還有一個神來之筆就是用字典了。數據庫

const result = 'a';
// bad
if(result == "a"){
  ...
}else if(result == "b"){
   ...
}else{
  ...
}
//best 咱們採用字典
interface IDictionaries {
  [key: string]: any;
}

const obj: IDictionaries = {
  a: () => {},
  b: () => {},
};
const fn = obj[result];
fn && fn();

複製代碼

3. Duplicate Observed Data(複製「被監視數據」) 

描述🐥:你有一些數據置身在GUI控件中(HTML頁面)中,而領域函數(在服務端、NW開發的PC端等)須要訪問這些數據。將該數據複製到一個領域對象中,創建一個Observer模式,用以同步領域對象和GUI對象內的重複數據。後端

上文提到了領域對象,對於不少Web前端的同窗是很模糊的,咱們來一塊兒學習下吧。數組

「領域對象(Domain Object)也被稱爲實體類,它表明了業務的狀態,且貫穿展示層、業務層和持久層,並最終持久化到數據庫,若是隻是簡單的理解的話,領域對象能夠看作是數據庫表的對應java類。若是對應到你們的前端開發裏也就是咱們Vue裏的data啦。markdown

說到了領域對象就必定要談一下領域驅動模型。業務邏輯都是寫在Service中的,WmActPoi充其量只是個數據載體(Vue裏的data只是定義了數據的基本形態,所有業務處理邏輯都堆積在了method裏)沒有任何行爲,是一種貧血模型,下圖呢就是DDD的總體運行流程。

採用DDD的設計思想,業務邏輯再也不集中在幾個大型的類上,而是由大量相對小的領域對象(類)組成,這些類具有本身的狀態和行爲,每一個類是相對完整的獨立體,並與現實領域的業務對象映射。領域模型就是由這樣許多的細粒度的類組成。以下我貼上一段很是核心的TypeScript實戰DDD的代碼。

看不懂的同窗你們能夠關注下@node-ts/ddd

// user.ts
import { AggregateRootProperties, AggregateRoot, Uuid } from '@node-ts/ddd'
import { UserRegistered, UserPasswordChanged, UserDisabled } from './events'
import { OAuthService } from './services'

export interface UserProperties extends AggregateRootProperties {
  email: string
  isEnabled: boolean
  passwordChangedAt: Date | undefined
}

export class User extends AggregateRoot implements UserProperties {
  email: string
  isEnabled: boolean
  passwordChangedAt: Date | undefined

  // Creation static method. Aggregates are never "newed" up by consumers.
  static register (id: Uuid, email: string): User {
    const userRegistered = new UserRegistered(
      id,
      email,
      true
    )

    const user = new User(id)
    // event is applied to the user object
    user.when(userRegistered)
    return user
  }

  /** * Changes the user's password that's used to log in to the site * @param oauthService the oauth service that hosts the user account * @param newPassword password the user wants to use */
  async changePassword (oauthService: OAuthService, newPassword: string): Promise<void> {
    // A domain service is used to perform the actual change of password
    await oauthService.changePassword(this.id, newPassword)

    const userPasswordChanged = new UserPasswordChanged(
      this.id,
      new Date()
    )
    super.when(userPasswordChanged)
  }

  /** * Disable the user account so they can no longer log in */
  disable (): void {
    const userDisabled = new UserDisabled(this.id, false)
    super.when(userDisabled)
  }
  
  protected whenUserRegistered (event: UserRegistered): void {
    this.email = event.email
    this.isEnabled = event.isEnabled
  }

  protected whenPasswordChanged (event: UserPasswordChanged): void {
    this.passwordChangedAt = event.passwordChangedAt
  }

  protected whenUserDisabled (event: UserDisabled): void {
    this.isEnabled = event.isEnabled
  }
}
複製代碼

動機🐈:一個分層良好的系統,應該將處理用戶界面和處理業務邏輯的代碼分開。若果你遇到的代碼是以兩層方式開發,業務邏輯被內嵌在用戶的界面之中,你就有必要將行爲分離出來,其中重要的任務就是函數的分解和搬移。但數據就不一樣了:你不能僅僅只是移動數據,必須將它複製到新的對象中,並提供相同的同步機制。

// 其實這個規則說成前端的大白話就是若是一個數據先後都須要 就把他設置成可Observable
// 目前的前端開發處理這個有Vue 、Mobx等等。可是單獨處理這個有Rx.js 
const locations = new RX.Observable((observer) => {
    let watchId: number;
    if ('geolocation' in navigator) {
      watchId = navigator.geolocation.watchPosition((position: Position) => {
        observer.next(position);
      }, (error: PositionError) => {
        observer.error(error);
      });
    } else {
      observer.error('Geolocation not available');
    }
  
    return {
      unsubscribe() {
        navigator.geolocation.clearWatch(watchId);
      }
    };
  });
  
  const locationsSubscription = locations.subscribe({
    next(position:Position) {
      console.log('Current Position: ', position);
    },
    error(msg:string) {
      console.log('Error Getting Location: ', msg);
    }
  });
  
  setTimeout(() => {
    locationsSubscription.unsubscribe();
  }, 10000);

複製代碼

如上的demo監聽的是定位信息,固然這個定位也能夠是後端發起請求的數據,說白了多端一塊兒監聽這個locations,而不是分散到各自的類中各自維護。

4. Encapsulate Collection(封裝集合) 

描述🌾:讓一個函數返回該集合的一個只讀副本,並在這個類中提供添加、移除集合元素的函數。

動機🐻:咱們經常會在一個類中使用集合(Map、Set、Array),一般這樣的類也會提供針對該集合的取值、設值函數。這個時候,取值函數不該該返回集合自己,由於這樣會讓用戶得以修改集合內容而集合擁有者缺一無所知,這樣也會暴露過多對象內部數據結構的信息。另外設置函數不該該提供添加修改和移除的操做,但不能直接重寫該集合。若是作到了這些集合就被很好的封裝了起來,這樣即可以下降集合永州這和用戶之間的耦合度。

這個規則不是很複雜,老袁就不給你們列具體的 code了。

前端圈最好用的刷題工具

掃碼 get 前端圈刷題神器,解鎖800+道 有答案和解析的 前端面試真題,所有來自BAT、TMD等一二線互聯網公司。


回顧第一天的文章👉 :《重構-代碼整潔之道TypeScript版》第一天

回顧次日的文章👉 :《重構-代碼整潔之道TypeScript版》次日

回顧第三天的文章👉 :《重構-代碼整潔之道TypeScript版》第三天

每一次咱們不會給你們寫過多的重構規則,力求天天用幾分鐘時間真正去理解了重構。明天見~若是您有什麼意見和反饋,歡迎您在下方留言。

做者 【老袁】 2020 年 07月 31日

相關文章
相關標籤/搜索