博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C#中面向对象编程中的函数式编程
阅读量:3528 次
发布时间:2019-05-20

本文共 10204 字,大约阅读时间需要 34 分钟。

目录


介绍

 

使用函数式编程来丰富面向对象编程的想法是陈旧的。将函数编程函数添加到面向对象的语言中会带来面向对象编程设计的好处。

一些旧的和不太老的语言,具有函数式编程和面向对象的编程:

  • 例如,SmalltalkCommon Lisp
  • 最近是PythonRuby

面向对象编程中仿真的函数式编程技术

面向对象编程语言的实践包括函数编程技术的仿真:

  • C ++:函数指针和()运算符的重载
  • Java:匿名类和反射

粒度不匹配

函数编程和面向对象编程在不同的设计粒度级别上运行:

  • 函数/方法:小程序编程
  • /对象/模块:大规模编程

至少有两个问题:

  • 我们在面向对象的编程体系结构中如何定位各个函数的来源?
  • 我们如何将这些单独的函数与面向对象的编程体系结构联系起来?

面向对象的函数式编程构造

C#提供了一个名为delegate 的函数编程函数:

delegate string StringFunType(string s); // declarationstring G1(string s){ // a method whose type matches StringFunType  return "some string" + s;}StringFunType f1;    // declaration of a delegate variablef1 = G1;             // direct method value assignmentf1("some string");   // application of the delegate variable

Delegate是一流的值。这意味着delegate类型可以键入方法参数,并且delegate可以作为任何其他值的参数传递:

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

Delegate可以作为方法的计算返回。例如,假设G是一个string=> string类型的方法,并在SomeClass中实现:

StringFunType Gf2(){ // delegate as a return value  [ ... ]  return (new SomeClass()).G;}Console.WriteLine(Gf2()("Boo")); // call

Delegate可以发生在数据结构中:

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

Cdelegate可能是匿名的:

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

匿名delegate看起来更像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 methodSimpleWordCount(s1);  // also usable as a standalone method

扩展方法的另一个例子:

static IEnumerable
MySort
(this IEnumerable
obj) where T:IComparable
{ [ ... ]}List
someList = [ ... ];someList.MySort();

扩展方法在C#中有严格的限制:

  • 只有静态
  • 不是多态的

C#中的函数式编程集成

C#为参数数量提供函数和程序泛型delegate预定义类型,最多

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

一个delegate本身可以包含delegate 的调用列表。当调用这样的delegate时,delegate中包含的方法将按照它们在列表中出现的顺序被调用。结果值由列表中调用的最后一个方法确定。

C#允许将lambda表达式表示为称为表达式树的数据结构:

Expression
> 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
Map
(this IEnumerable
data, Func
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
(); 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
res = (Dictionary
) 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 });

函数部分应用和局部套用

使用第一类函数,每个n元函数都可以转换为n个一元函数的组合,即成为一个局部套用函数:

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

局部套用:

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

面向对象编程中的体系结构函数编程技术

在面向对象编程中具有函数编程函数的一些架构效果:

  1. 减少对象/类定义的数量
  2. 在函数/方法级别命名抽象
  3. 作业构成(和序列理解)
  4. 函数部分应用和局部套用

一些经典的面向对象设计模式与函数编程

 

为什么函数式编程通常集成到面向对象的编程中?

主要的面向对象编程语言基于类作为模块:C#,C ++Java

面向对象编程开发的强大思想之一:维护,扩展和适应操作可以通过继承和类组合。(这避免了对现有代码的任何修改。)函数式编程是解决此问题的方法。

例如,策略设计模式。

策略

策略模式允许算法独立于使用它的客户端而变化。

 

策略:只是在方法级别抽象代码的情况(不需要面向对象的封装和新的类层次结构)。例如,在.NET Framework中:

public delegate int Comparison
(T x, T y);public void Sort(Comparison
comparison);public delegate bool Predicate
(T obj);public List
FindAll(Predicate
match);

 

其他设计模式,如命令,观察者,访问者和虚拟代理,可以使一流的函数受益:

 

命令

命令模式将请求(方法调用)封装为对象,以便可以轻松地传输,存储和应用它们。例如,菜单实现:

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);}

 

观察者

对象之间的一对多依赖关系,以便当一个对象更改状态时,将通知并更新其所有依赖项。

 

下面是观察者设计模式的经典实现:

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

函数编程:

public delegate void UpdateFun(S s);public abstract class Subject{  private UpdateFun _updateHandler;  public void Attach(UpdateFun f){    _updateHandler += f;  }  public void Notify(S s){    _updateHandler(s);  }}

我们可以看到,不需要使用调用Update方法的观察者类。

 

虚拟代理

虚拟代理模式:其他对象的占位符,以便仅在需要时创建/计算其数据。

 

以下是虚拟代理设计模式的经典实现:

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
_simpleLazy; public SimpleLazyProxy(int i){ _simpleLazy = new Lazy
(() => new Simple(i)); } public void Process(){ _simpleLazy.Value.Process(); }}

访问者

访问者模式允许您定义新操作,而无需更改其操作元素的类。如果没有访问者,则必须单独编辑或派生层次结构的每个子类。访客是许多编程设计问题的关键。

 

以下是访问者设计模式的经典实现:

public interface IFigure{  string GetName();  void Accept
(IFigureVisitor
v);}public class SimpleFigure : IFigure{ private string _name; public SimpleFigure(string name){ _name = name; } public string GetName(){ return _name; } public void Accept
(IFigureVisitor
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
(IFigureVisitor
v){ foreach (IFigure f in _figureArray) f.Accept (v); v.Visit(this); }}public interface IFigureVisitor
{ T GetVisitorState(); void Visit(SimpleFigure f); void Visit(CompositeFigure f);}public class NameFigureVisitor : IFigureVisitor
{ private string _fullName = " "; public string GetVisitorState(){ return _fullName; } public void Visit(SimpleFigure f){ _fullName += f.GetName() + " "; } public void Visit(CompositeFigure f){ _fullName += f.GetName() + "/"; }}

一些Visitor众所周知的弱点:

  • 重构阻力Visitor定义取决于其运行的客户端类集。
  • 过分静态化:一个Visitor是在其执行中的(类型安全,但灵活性较差)static
  • 入侵性Visitor需要客户类预期和/或参与选择正确的方法。
  • 命名不灵活Visitor需要对visit方法的所有不同实现进行类似命名。

试图解决Visitor扩展方法的问题:

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...    [ ... ]  }}

通过函数式编程,Visitor可以是函数:

public delegate T VisitorFun
(V f);public interface IFigureF{ string GetName (); T Accept
(VisitorFun
v);}public class SimpleFigureF : IFigureF{ private string _name ; public SimpleFigureF(string name){ _name = name ; } public string GetName(){ return _name ; } public T Accept
(VisitorFun
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
(VisitorFun
v){ foreach(IFigureF f in _figureArray) f.Accept(v); return v(this); }}

 

以下是一个简单的函数Visitor

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

以下是面向数据的Visitor

var dict1 = new Dictionary
>();dict1.Add(typeof(SimpleFigureF), f => f.GetName() + " ");dict1.Add(typeof(CompositeFigureF), f => f.GetName() + "/");var nameFigureFunVisitor1 = MakeVisitorFun
(dict1);

我们可以看到,通过函数编程和数据驱动编程,重构阻力更小,名称刚性更小,静态更少。

总结

面向对象的函数编程粒度级编程:

  • 函数式编程适用于模块化对象
  • 函数/方法级别的代码抽象
  • 方便的泛型迭代器/循环实现
  • 操作组合,序列/查询理解
  • 函数部分应用
  • 对象/类定义数量的限制
  • 在函数/方法级别命名抽象
  • 懒惰模拟(在虚拟代理中使用)
  • 数据驱动编程(在访问者中使用)
  • 架构简化
  • 增加灵活性

将函数编程能力添加到面向对象的语言中会带来面向对象编程设计的好处。

 

原文地址:

转载地址:http://cxzhj.baihongyu.com/

你可能感兴趣的文章
linux VM虚拟机可以ping通主机,但主机无法ping通虚拟机
查看>>
linux 错误码
查看>>
C++ 中Struct与typedef struct总结
查看>>
WNetAddConnection2调用失败,错误码1200/1312
查看>>
POI读写Excel的基本使用
查看>>
淘宝网站的架构演进
查看>>
设置zookeeper开机自启动流程
查看>>
CentOS安装mysql5.7的教详细流程
查看>>
项目整合微信扫码登录功能
查看>>
分布式文件系统FastDfs的搭建
查看>>
Springboot项目利用Java客户端调用FastDFS
查看>>
全文检索工具elasticsearch的安装和简单介绍
查看>>
利用Kibana学习全文检索工具elasticsearch
查看>>
SpringBoot在Test测试类或自定义类中通过@Autowired注入为null
查看>>
使用docker搭建YAPI服务
查看>>
西南科技大学OJ题 邻接表到邻接矩阵1056
查看>>
西南科技大学OJ题 有向图的出度计算1057
查看>>
西南科技大学OJ题 有向图的最大出度计算1059
查看>>
西南科技大学OJ题 带权有向图计算1063
查看>>
oracle主键自增触发器编写
查看>>