由擴展方法引申出的編程思維

作者: 飛林沙  來源: 博客園  發布時間: 2011-03-23 10:37  閱讀: 1137 次  推薦: 0   原文鏈接   [收藏]  

  1. Helper大爆炸

  .NET Framework為我們提供了豐富的類庫,但是這并不是萬能地,在大部分的時間,我們都需要為我們的項目特殊定制我們的通用類庫。

  常常,我們都可以構造一個類,類里封裝一些方法。但是對于很多時候,我們并沒有辦法提取出這樣一個類,舉一個小例子,我們在很多時候,需要把url給保存到數據庫里,作為一個唯一標識,但是我們知道url所占空間很大,如果用url來建立索引的話是非常耗費空間,而且影響效率的,那么我們最常用的辦法就是把url做一個Hash來作為索引的替代品。

  這個時候,我們根本就沒有辦法說我們來怎么樣提取一個類,然后在類里寫這樣一個方法,這個時候,我們通常就只能這樣:

 
public static class HashHelper
{

public static string GetHashCode(string s)
{

//GetHashCode........
return String.Empty
}
}

  然后我們會這樣使用:

 
public static void Main(string[] args)
{

string url = "www.fandongxi.com"
string sql = "insert into Test values('"+HashHelper.GetHashCode(url)+"')"
//執行SQL
}

  這里,只是一個例子,并不是說我們要這個樣子拼接字符串。

  很快,肯定又會出現一個情況,說,我們要保存網頁的內容,但是網頁的內容直接存儲到數據庫里太大了,那么我們就需要對網頁文本做一個Base64的壓縮。

  那么,我們就又得繼續寫:

 
public static class Base64Helper
{

public static string GetBase64Text(string text)
{

//Base64........
return String.Empty
}
}

  接下來我們在使用的地方就又多出來一個Base64Helper。那么過幾天,還會出現SHA1Helper , MD5Helper等等各種各樣的Helper。

  漸漸地,我們會不會發現,Helper的數量已經讓我們難以忍受了呢?

  2. 擴展方法的提出

  接下來的事情,我們都知道了,在.NET Framework 3.5中,也就是在C#3.0中,引入了擴展方法這個概念。

  那就讓我們擴展方法來解決上面的難題。

  各位現在一定知道,無論是做UrlHashCode,還是Base64壓縮,還是SHA1加密,還是MD5加密,這些都是針對字符串,或者說是一段文本的處理,那么很自然地,我們就需要把這些全部寫入String類的擴展方法中。

 
public static class ExtensionClass
{

public static string GetHashCode(this string s)
{

//........
}
public static string GetBase64Text(this string text)
{

//.......
}
}


public static void Main(string[] args)
{

string url = "www.fandongxi.com"
string sql = "insert into Test values('"+url.GetHashCode()+"')"
//執行SQL
}

  在這里,我不想剖析去讀擴展方法的實現本質,這里我們只談編程思維和擴展方法所帶來的意義。

  3. 擴展方法讓C#更加面向對象

  從面向對象的角度來看,世間萬物皆為對象,所有屬性,所有方法都是屬于某一個對象的,那么再從這個角度看開去,本就不應該存在靜態類,也不應該存在靜態方法,所謂的靜態,不過是面向對象語言對并不成熟的語法實現的一種屈從罷了。

  我們要求Base64加密后的文本,其實是文本調用自身的一個方法,之所以我們在之前的方法中需要一個Base64Helper,而不能這樣子"http://www.fandongxi.com%22.replace(%22com%22,%22cn/")直接調用,只是因為.NET Framework無法預計到我們所有的業務場景,所以把只能把最通用的方法封裝到已有的類庫中。

  4. 從擴展方法向外談一些

  讓我們從擴展方法逐漸地向外圍來探討一些關于編碼規范,以及一些代碼優雅的問題。我們先不妨假設我們并不存在“+”運算符,或者說,我們禁止在程序中使用+運算符,那么也就是說,我們需要對“+”這個操作來做一個簡單的封裝,那么我們常規意義上會怎么做?

 
public int Add(int a,int b)
{

return a+b;
}


public static void Main(string[] args)
{

int result = Add(3,4)
Console.WriteLine(result)
}

  讓我們來看這個函數,我們順著代碼的意思向下讀,加,3,4。這明顯是不符合我們常規的數學思維的,如果用了擴展方法之后,我們一定是應該這樣來寫。

 
public static class Extension
{

public static int Add(this int a,int b)
{

return a+b;
}
}


public static void Main(string[] args)
{

int a = 3;
a.Add(b)
}

  可是這個"."運算符看上去還是那么有點別扭…..沒辦法,至少這樣讀上去讓我們的代碼順暢了很多不是么?像寫文章,說話一樣寫代碼一直是我們程序員追求的最高境界,就像這樣的代碼總是好的。

  Good:people.eat(food)

  而不是Bad:Eat(people,food)

  對把!

  5. 前綴,中綴和后綴表達式

  說到這,就不得不談談前綴,中綴和后綴表達式了。

  學過數據結構的朋友們,一定都記得在數據結構中,有一道經典的習題,就是利用“棧”來實現前綴,中綴和后綴表達式的轉換。在考試題中也經常會出現這樣的習題。那現在讓我們來復習一下,什么是前綴,中綴和后綴表達式。

前綴表達式就是不含括號的算術表達式,而且它是將運算符寫在前面,操作數寫在后面的表達式,也稱為“波蘭式”。

  大名鼎鼎的Lisp就是前綴表達式的典型,讓我們看一個最簡單的小例子,還是那個經典的斐波那契數列:

 
(define (fib n)
(fib
-iter 1 0 n))

(defile (fib
-iter a b count)
(
if (= count 0)
b
(fib
-iter (+ a b) a (- count 1))))

  每次寫Lisp的時候,都會被密密麻麻的括號所嚇到,可是真的沒什么太好的解決方案呢!

  中綴表達式就很簡單了,和我們常規所涉及到的代碼是一樣的,后綴也是一個道理,在此就不再一一贅述。鑒于后綴的應用不是很大,在此我們也只談談前綴和中綴的意義。

  那么我們想想,為什么Lisp要采用這么蹩腳的前綴表達式語法呢?

  記得在大二第一次學習C語言的時候,老師讓我們寫一個簡單的計算器,當時每個同學都寫出了+,-,*,/的操作,但是在當時大多數的我們都沒有辦法寫出更為常用的混合運算,以及()的操作,當時只有班上某鶴立雞群的哥們寫出了讓我們當時完全無法看懂的代碼。再直到大三學習數據結構,再反過來想他當時的代碼,才恍然大悟。

  廢話說了一堆,那么其實前綴表達式最大的意義就是他更貼近計算機的思維,他只需要兩種操作就能完成運算,就是入棧和出棧。讓我們來看一個簡單的小例子

  3+(1-4),首先這是一個中綴表達式,把他轉換為前綴表達式就是+3 – 1 4,計算機會從右向左來掃描這個表達式,4入棧,1入棧,然后遇到 - ,1和4出棧,并且完成運算,(-3)入棧,3進棧,+入棧,(-3)和3出棧,完成運算。

  也就是說,其實在計算機完成我們所編寫的數學操作時,其實往往都是把我們的中綴表達式首先轉換為前綴表達式,然后完成計算,而Lisp采用前綴表達式,則是省去了這一個步驟,從而提高解釋器的效率。

  那我們就來總結下前綴和中綴表達式的意義。

  前綴表達式更加貼近計算機思維,方便計算。而中綴表達式更加貼近數學思維,容易被我們所理解。

  那回顧下,我們之前寫Add的代碼,如果說我們去掉.運算符,而且方法不加括號,是否采用擴展方法,把C#的語法和Lisp的語法相結合,其實就成了這樣的形式。

 
public int Add(int a,int b)
{

return a+b;
}

public static void Main(string[] args)
{
(
set! result (Add a b))
}

public static class Extension
{

public static int Add(this int a,int b)
{

return a+b;
}
}

public static void Main(string[] args)
{
(
set! result (a Add b))
}

  還是后者更貼近我們的自然思維一些。

  .NET Framework很強大,給我們提供了擴展方法這個概念,那么如果沒有了擴展方法,其他語言給出了怎么樣的解決方案呢?

  那讓我們來看看Haskell給出的方案。

  5. 看看Haskell的方法

  Haskell是一門函數式的語言,在FP大行其道的今天,Haskell這門久居深宮的語言也漸漸地浮出了水面。

  廢話不多說,我們只來看看Haskell是如何在沒有擴展方法的情況下來解決語法和自然思維不相協調的問題的。

  讓我們先來編寫一個簡單的Haskell函數。

  add x y = x + y

  代碼很簡單,沒什么值得多說,讓我們來看看Haskell怎么調用。

image  這是我們傳統的調用方式,可是Haskell為了更貼近我們的自然思維,為參數個數數量為2的方法提供了這樣一個便捷的調用:

image  這就是Haskell為我們提供的“中綴表達式”的解決方案。

  擴展方法很好,但是當我們的語言中沒有擴展方法的時候,Haskell給我們提供了一個優秀的典范。

  6. 語言和類庫

  說到這,我就想順便談談關于語言擴展和類庫擴展的問題。

  在《Masterminds of Programmming》一書中,Python語言之父Guido在接受采訪時,談到PEP(Python增強處理)時,順便說到了關于在編寫編程語言時,如何來根據用戶的意見來處理語言實現的問題。

  他談到:

如果某個用戶提出一個新特性,它幾乎不會成功。因為用戶對實現沒有全面的理解,他幾乎不可能提出一個合理的新特性。

  那么在我看來什么是用戶?用戶就是使用這門語言來完成工作任務的人,他們往往需要的都是增加一個新功能,換句話說,他們需要的僅僅是一個方法而已。

  那么什么是增加語言特性,什么是增加類庫方法,Guido也給出了比較合理的解釋。

如果某個特性對于Web來說確實很棒,那么,對于加到語言中來說,就未必是優秀的特性了。如果它確實利于編寫更短的函數,或者是有利于編寫可維護更強的類,把它添加到語言中可能就是一件好事。

  其實Guido的意思很簡單,是否增加到語言中,關于在于這個特性是否是領域相關的,如果是領域相關的,也許它需要做的僅僅是擴展類庫,無論是增加Python的類庫,還是用C去擴展Python API,總之無需對語言做出改變。

  那么對于C#來說,什么是類庫的修改,什么是語言的修改,在我看來,每一個版本的修改都一定有著類庫的修改,但是如果說到語言的修改,應該是僅僅當MSIL發生變動的時候,我們才可以說語言發生了修改。//仔細想了一下,這個觀點有問題....但是我沒找到更合適的語言來做比喻。也許應該說,只有當語法的編譯規則發生改變的時候,我們才可以說語言發生了修改。

  Python也是一樣,增加了方法充其量是類庫的修改,而僅僅是語言的解釋過程都發生了修改才可以算得上是語言層面的修改,例如從Python 2.x到Python3.x的大版本變動。

  7. 總結

  在本文中,主要是從擴展方法說起,談到我們該怎么樣更好的編寫更貼近自然語言的程序。

  然后再到一些沒有擴展方法語言給出的折衷實現。而對于Python,C等其他語言,我尚且沒有找到合適的方法來解決問題。

  如果各位有好的辦法,尤其是對于Python,畢竟這是我的工作,希望各位補充給出解決方法。

  謝謝。

0
0
 
標簽:c# 擴展方法
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()