C#中面向對象編程中的函數式編程詳解

来源:https://www.cnblogs.com/langda/archive/2019/07/23/11235269.html
-Advertisement-
Play Games

介紹 使用函數式編程來豐富面向對象編程的想法是陳舊的。將函數編程功能添加到面向對象的語言中會帶來面向對象編程設計的好處。 一些舊的和不太老的語言,具有函數式編程和麵向對象的編程: 例如,Smalltalk和Common Lisp。 最近是Python或Ruby。 面向對象編程中模擬的函數式編程技術 ...


介紹

使用函數式編程來豐富面向對象編程的想法是陳舊的。將函數編程功能添加到面向對象的語言中會帶來面向對象編程設計的好處。

一些舊的和不太老的語言,具有函數式編程和麵向對象的編程:

  • 例如,Smalltalk和Common Lisp。
  • 最近是Python或Ruby。

面向對象編程中模擬的函數式編程技術

面向對象編程語言的實踐包括函數編程技術的模擬:

  • C ++:函數指針和()運算符的重載。
  • Java:匿名類和反思。

粒度不匹配

功能編程和麵向對象編程在不同的設計粒度級別上運行:

  • 功能/方法:在小程度上編程。
  • 類/對象/模塊:大規模編程。

Threre至少有兩個問題:

  • 我們在面向對象的編程體繫結構中如何定位各個函數的來源?
  • 我們如何將這些單獨的函數與面向對象的編程體繫結構聯繫起來?

面向對象的函數式編程構造

C#提供了一個名為delegates的函數編程功能:

delegate string StringFunType(string s); // declaration

string G1(string s){ // a method whose type matches StringFunType
  return "some string" + s;
}

StringFunType f1;    // declaration of a delegate variable
f1 = G1;             // direct method value assignment
f1("some string");   // application of the delegate variable

代表是一流的價值觀。這意味著委托類型可以鍵入方法參數,並且委托可以作為任何其他值的參數傳遞:

string Gf1(StringFunType f, string s){ [ ... ] } // delegate f as a parameter
Console.WriteLine(Gf1(G1, "Boo"));   // call

代理可以作為方法的計算返回。例如,假設G是一個string => string類型的方法,併在SomeClass中實現:

StringFunType Gf2(){ // delegate as a return value
  [ ... ]
  return (new SomeClass()).G;
}

Console.WriteLine(Gf2()("Boo")); // call

代表可以進入數據結構:

var l = new LinkedList<StringFunType>(); // list of delegates
[ ... ]
l.AddFirst(G1) ; // insertion of a delegate in the list
Console.WriteLine(l.First.Value("Boo")); // extract and call

C#代表可能是匿名的:

delegate(string s){ return s + "some string"; };

匿名委托看起來更像lambda表達式:

s => { return s + "some string"; }; 
s => s + "some string";

相互關係函數式編程/面向對象程式設計

擴展方法使程式員能夠在不創建新派生類的情況下向現有類添加方法:

static int SimpleWordCount(this string str){
  return str.Split(new char[]{' '}).Length;
}

string s1 = "some chain";
s1.SimpleWordCount(); // usable as a String method
SimpleWordCount(s1);  // also usable as a standalone method

擴展方法的另一個例子:

static IEnumerable<T> MySort<T>(this IEnumerable<T> obj) where T:IComparable<T>{
  [ ... ]
}

List<int> someList = [ ... ];
someList.MySort();

擴展方法在C#中有嚴格的限制:

  • 只有靜態
  • 不是多態的

C#中的函數式編程集成

C#為arity提供功能和程式通用委托預定義類型,最多16個

delegate TResult Func<TResult>();
delegate TResult Func<T, TResult>(T a1);
delegate TResult Func<T1, T2, TResult>(T1 a1, T2 a2);
delegate void Action<T>(T a1);
[ ... ]

委托本身可以包含委托的調用列表。調用此委托時,委托中包含的方法將按它們在列表中出現的順序調用。結果值由列表中調用的最後一個方法確定。

C#允許將lambda表達式表示為稱為表達式樹的數據結構:

Expression<Func<int, int>> expression = x => x + 1;
var d = expression.Compile();
d.Invoke(2);

因此,它們可以被存儲和傳輸。

功能級別的代碼抽象

一個簡單的代碼:

float M(int y){
  int x1 = [ ... ]; 
  int x2 = [ ... ];
  [ ... ]
  [ ... some code ... ]; // some code using x1, x2 and y
  [ ... ]
}

功能抽象:

public delegate int Fun(int x, int y, int z);
float MFun(Fun f, int x2, int y){
  int x1 = [ ... ];
  [ ... ]
  f(x1, x2, y);
  [ ... ]
}

int z1 = MFun(F1, 1, 2);
int z2 = MFun(F2, 1, 2);

功能抽象的優點是沒有局部重覆,並且存在關註點分離。

功能抽象的簡單有效應用是對數據的通用高階迭代操作。

例如,內部迭代器(Maps):

IEnumerable<T2> Map<T1, T2>(this IEnumerable<T1> data, Func<T1, T2> f){
  foreach(var x in data)
    yield return f(x);
}

someList.Map(i => i * i);

運營組成

在功能編程中,操作組合物很容易。初始代碼:

public static void PrintWordCount(string s){
  string[] words = s.Split(' ');

  for(int i = 0; i < words.Length; i++)
    words[i] = words[i].ToLower();

  var dict = new Dictionary<string, int>();

  foreach(var word in words)
    if (dict.ContainsKey(word))
      dict[word]++;
    else 
      dict.Add(word, 1);

  foreach(var x in dict)
    Console.WriteLine("{0}: {1}", x.Key, x.Value.ToString());
}

使用高階函數的第一個因數

public static void PrintWordCount(string s){
  string[] words = s.Split(' ');
  string[] words2 = (string[]) Map(words, w => w.ToLower());
  Dictionary<string, int> res = (Dictionary<string, int>) Count(words2);
  App(res, x => Console.WriteLine("{0}: {1}", x.Key, x.Value.ToString()));
}

使用擴展方法的第二個因數:

public static void PrintWordCount(string s){
  s
  .Split(' ')
  .Map(w => w.ToLower())
  .Count()
  .App(x => Console.WriteLine("{0}: {1}", x.Key, x.Value.ToString()));
}

我們可以看到代碼的可讀性增加了。

在C#中,這種操作組合通常與LINQ一起使用,LINQ被定義為將編程與關係數據或XML統一起來。下麵是一個使用LINQ的簡單示例:

var q = programmers
.Where(p => p.Age > 20)
.OrderByDescending(p => p.Age)
.GroupBy(p => p.Language)
.Select(g => new { Language = g.Key, Size = g.Count(), Names = g });

功能部分應用和Currying

使用第一類函數,每個n元函數都可以轉換為n個一元函數的組合,即成為一個curried函數:

Func<int, int, int> lam1 = (x, y) => x + y;
Func<int, Func<int, int>> lam2 = x => (y => x + y);
Func<int, int> lam3 = lam2(3) ; // partial application

柯里:

public static Func<T1, Func<T2, TRes>> Curry<T1, T2, TRes>(this Func<T1, T2, TRes> f){
    return (x => (y => f(x, y)));
}
Func<int, int> lam4 = lam1.Curry()(3); // partial application

面向對象編程中的體繫結構功能編程技術

在面向對象編程中具有函數編程功能的一些架構效果:

  1. 減少對象/類定義的數量。
  2. 在函數/方法級別命名抽象。
  3. 操作組合(和序列理解)。
  4. 功能部分應用和currying。

一些經典的面向對象設計模式與功能編程

 

為什麼函數式編程通常集成到面向對象的編程中?

主要的面向對象編程語言基於類作為模塊:C#,C ++,Java。

面向對象編程中開發的強大思想之一:維護,擴展和適應操作可以通過繼承和類組合(這避免了對現有代碼的任何修改)。函數式編程源碼是這個問題的解決方案。

例如,戰略設計模式。

戰略

策略模式允許演算法獨立於使用它的客戶端而變化。

C#中面向對象編程中的函數式編程詳解

 

策略:只是在方法級別抽象代碼的情況(不需要面向對象的封裝和新的類層次結構)。例如,在.NET Framework中:

public delegate int Comparison<T>(T x, T y);
public void Sort(Comparison<T> comparison);

public delegate bool Predicate<T>(T obj);
public List<T> FindAll(Predicate<T> match);

C#中面向對象編程中的函數式編程詳解

其他設計模式,如命令,觀察者,訪問者和虛擬代理,可以使一流的功能受益:

C#中面向對象編程中的函數式編程詳解

命令

Command模式將請求(方法調用)封裝為對象,以便可以輕鬆地傳輸,存儲和應用它們。例如,菜單實現:

public delegate void EventHandler(object sender, EventArgs e);
public event EventHandler Click;

private void menuItem1_Click(object sender, EventArgs e){
  OpenFileDialog fd = new OpenFileDialog();
  fd.DefaultExt = "*.*" ; 
  fd.ShowDialog();
}

public void CreateMyMenu(){
  MainMenu mainMenu1 = new MainMenu();
  MenuItem menuItem1 = new MenuItem();
  [ ... ]
  menuItem1.Click += new EventHandler(menuItem1_Click);
}

C#中面向對象編程中的函數式編程詳解

觀察

對象之間的一對多依賴關係,以便當一個對象更改狀態時,將通知並更新其所有依賴項。

C#中面向對象編程中的函數式編程詳解

下麵是觀察者設計模式的經典實現:

public interface Observer<S>{
  void Update(S s);
}

public abstract class Subject<S>{
  private List<Observer<S>> _observ = new List<Observer<S>>();
  public void Attach(Observer<S> obs){
    _observ.Add(obs);
  }
  public void Notify(S s){
    foreach (var obs in _observ)
      obs.Update(s);
  }
}

功能編程:

public delegate void UpdateFun<S>(S s);

public abstract class Subject<S>{
  private UpdateFun<S> _updateHandler;

  public void Attach(UpdateFun<S> f){
    _updateHandler += f;
  }
  public void Notify(S s){
    _updateHandler(s);
  }
}

我們可以看到,不需要使用名為Update的方法的觀察者類。

C#中面向對象編程中的函數式編程詳解

虛擬代理

虛擬代理模式:其他對象的占位符,以便僅在需要時創建/計算其數據。

C#中面向對象編程中的函數式編程詳解

下麵是虛擬代理設計模式的經典實現:

public class SimpleProxy : I{
  private Simple _simple;
  private int _arg;

  protected Simple GetSimple(){
    if (_simple == null)
      _simple = new Simple(_arg);
    return _simple;
  }
  public SimpleProxy(int i){
    _arg = i ;
  }
  public void Process(){
    GetSimple().Process();
  }
}

下麵使用函數式編程和懶惰實現虛擬代理設計模式:

public class SimpleLazyProxy : I{
  private Lazy<Simple> _simpleLazy;

  public SimpleLazyProxy(int i){
    _simpleLazy = new Lazy<Simple>(() => new Simple(i));
  }
  public void Process(){
    _simpleLazy.Value.Process();
  }
}

游客

訪問者模式允許您定義新操作,而無需更改其操作元素的類。如果沒有訪問者,則必須單獨編輯或派生層次結構的每個子類。訪客是許多編程設計問題的關鍵。

C#中面向對象編程中的函數式編程詳解

以下是訪問者設計模式的經典實現:

public interface IFigure{
  string GetName();
  void Accept<T>(IFigureVisitor<T> v);
}

public class SimpleFigure : IFigure{
  private string _name;

  public SimpleFigure(string name){ 
    _name = name;
  }
  public string GetName(){ 
    return _name;
  }
  public void Accept<T>(IFigureVisitor<T> v){
    v.Visit(this);
  }
}

public class CompositeFigure : IFigure{
  private string _name;
  private IFigure[] _figureArray;

  public CompositeFigure(string name, IFigure[] s){
    _name = name; 
    _figureArray = s;
  }
  public string GetName(){
    return _name;
  }
  public void Accept<T>(IFigureVisitor<T> v){
    foreach (IFigure f in _figureArray)
      f.Accept (v);
    v.Visit(this);
  }
}

public interface IFigureVisitor<T>{
  T GetVisitorState();
  void Visit(SimpleFigure f);
  void Visit(CompositeFigure f);
}

public class NameFigureVisitor : IFigureVisitor<string>{
  private string _fullName = " ";

  public string GetVisitorState(){
    return _fullName;
  }
  public void Visit(SimpleFigure f){
    _fullName += f.GetName() + " ";
  }
  public void Visit(CompositeFigure f){
    _fullName += f.GetName() + "/";
  }
}

訪客的一些眾所周知的弱點:

  • 重構阻力:訪客定義取決於其運行的客戶端源碼類集。
  • 靜態:訪問者的實現是靜態的(類型安全但靈活性較低)。
  • 入侵:訪問者需要客戶類預期和/或參與選擇正確的方法。
  • 命名不靈活:訪問者需要同樣命名訪問方法的所有不同實現。

嘗試使用擴展方法解決訪問者問題:

public interface IFigure{
  string GetName(); // no Accept method required
}
[ ... ]

public static class NameFigureVisitor{
  public static void NameVisit(this SimpleFigure f){ 
    _state = f.GetName() + " " + _state;
  }
  public static void NameVisit(this CompositeFigure f) {
    _fullName = f.GetName() + ":" + _fullName;
    foreach(IFigure g in f.GetFigureArray())
      g.NameVisit(); // dynamic dispatch required...
    [ ... ]
  }
}

通過函數式編程,Visitors可以是函數:

public delegate T VisitorFun<V, T>(V f);
public interface IFigureF{
  string GetName ();
  T Accept<T>(VisitorFun<IFigureF, T> v);
}

public class SimpleFigureF : IFigureF{
  private string _name ;

  public SimpleFigureF(string name){
    _name = name ;
  }
  public string GetName(){
    return _name ; 
  }
  public T Accept<T>(VisitorFun<IFigureF, T> v){
    return v(this);
  }
}
[...]

public class CompositeFigureF : IFigureF{
  private string _name;
  private IFigureF[ ] _figureArray;

  public CompositeFigureF(string name, IFigureF[] s){
    _name = name; 
    _figureArray = s;
  }
  public string GetName(){
    return this._name;
  }
  public T Accept<T>(VisitorFun<IFigureF, T> v){
    foreach(IFigureF f in _figureArray)
      f.Accept(v);
    return v(this);
  }
}

C#中面向對象編程中的函數式編程詳解

以下簡單功能訪客:

public static VisitorFun<IFigureF, string> MakeNameFigureVisitorFun(){
    string _state = "";
    return obj => {
      if(obj is SimpleFigureF)
        _state += obj.GetName() + " ";
      else if(obj is CompositeFigureF)
       _state += obj.GetName() + "/";
      return _state ;
   };
}

以數據為導向的訪問者:

var dict1 = new Dictionary<Type, VisitorFun<IFigureF, string>>();
dict1.Add(typeof(SimpleFigureF), f => f.GetName() + " ");
dict1.Add(typeof(CompositeFigureF), f => f.GetName() + "/");

var nameFigureFunVisitor1 = MakeVisitorFun<IFigureF, string>(dict1);

我們可以看到,通過功能編程和數據驅動編程,重構阻力更小,名稱剛性更小,靜態更少。

加起來

具有函數式編程粒度級別的對象 - 節點編程:

  • 函數式編程適用於模塊化對象。
  • 函數/方法級別的代碼抽象。
  • 方便的通用迭代器/迴圈實現。
  • 操作組合,序列/查詢理解。
  • 功能部分應用。
  • 對象/類定義數量的限制。
  • 在函數/方法級別命名抽象。
  • 懶惰模擬(在虛擬代理中使用)。
  • 數據驅動編程(在訪客中使用)。
  • 架構簡化。
  • 增加靈活性。

將函數編程功能添加到面向對象的語言中會帶來面向對象編程設計的好處。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 9.9 線程理論 1、什麼是線程 線程指的是一條流水線的工作過程 進程根本就不是一個執行單位,進程其實是一個資源單位,一個進程內自帶一個線程,線程才是執行單位 2、進程VS線程 同一進程內的線程們共用該進程內資源,不同進程內的線程資源肯定是隔離的 創建線程的開銷比創建進程要小的多 同一進程內的線程們 ...
  • 本文介紹如何利用Python+uiautomator2 每日自動賺取支付寶積分。 支付寶的積分有啥用?誘惑誘惑你: 可以兌換視頻網站的VIP會員。 可以兌換各種優惠券。 可以在年底活動中兌換蘋果手機。 其他,一言難盡... 比如,我喜歡買知識課堂的課程。(知識付費時代,我用積分買知識) 好了,有了動 ...
  • 一、 cookie 1. 定義:保存在瀏覽器本地上的一組組鍵值對 2. 特點: 由伺服器讓瀏覽器進行設置的 瀏覽器保存在瀏覽器本地 下次訪問時自動攜帶 3. 應用: 登錄 保存瀏覽習慣 簡單的投票 4. 使用cookie的原因:因為HTTP是無狀態的,用cookie來保存狀態 5. 在django中 ...
  • 本文介紹的Java規則的說明分為3個主要級別,中級是平時開發用的比較多的級別,在今後將陸續寫出其他的規則。遵守了這些規則可以提高程式的效率、使代碼又更好的可讀性等。 一、在finally方法里關掉input或者output資源 方法體裡面定義了input或者output流的話,需要在finally里 ...
  • T1 足球聯賽 題目 【題目描述】 巴蜀中學新一季的足球聯賽開幕了。足球聯賽有n只球隊參賽,每賽季,每隻球隊要與其他球隊各賽兩場,主客各一場,贏一場得3分,輸一場不得分,平局兩隻隊伍各得一分。 英勇無畏的小鴻是機房的主力前鋒,她總能在關鍵時刻踢出一些匪夷所思的妙球。但是很可惜,她過早的燃燒完了她的職 ...
  • hhh 為年薪20萬加油ヾ(◍°∇°◍)ノ゙ 一、變數:(變數的命名規則:一般使用字母開頭,可以使用下劃線連接,以及數字) 正確的變數命名示範: (儘量使用容易理解什麼用途的詞語) a1 name_Li name2 錯誤的變數示例: 1a a=1 print(a) b='年薪百萬不是夢' print ...
  • 一個可以沉迷於技術的程式猿,wx加入加入技術群:fsx641385712 ...
  • import os def file_handler(backend_data,res=None,type='fetch'): # 查詢功能 if type == 'fetch': with open('test_new.txt','r') as read_f: ret = [] ... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...