介绍

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

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

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

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

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

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

粒度不匹配

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

  • 功能/方法:在小程度上编程。
  • 类/对象/模块:大规模编程。

Threre至少有两个问题:

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

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

C#提供了一个名为delegates的函数编程功能:

  1. delegate string StringFunType(string s); // declaration
  2.  
  3. string G1(string s){ // a method whose type matches StringFunType
  4. return "some string" + s;
  5. }
  6.  
  7. StringFunType f1; // declaration of a delegate variable
  8. f1 = G1; // direct method value assignment
  9. f1("some string"); // application of the delegate variable

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

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

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

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

代表可以进入数据结构:

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

C#代表可能是匿名的:

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

匿名委托看起来更像lambda表达式:

  1. s => { return s + "some string"; };
  2. s => s + "some string";

相互关系函数式编程/面向对象程序设计

扩展方法使程序员能够在不创建新派生类的情况下向现有类添加方法:

  1. static int SimpleWordCount(this string str){
  2. return str.Split(new char[]{' '}).Length;
  3. }
  4.  
  5. string s1 = "some chain";
  6. s1.SimpleWordCount(); // usable as a String method
  7. SimpleWordCount(s1); // also usable as a standalone method

扩展方法的另一个例子:

  1. static IEnumerable<T> MySort<T>(this IEnumerable<T> obj) where T:IComparable<T>{
  2. [ ... ]
  3. }
  4.  
  5. List<int> someList = [ ... ];
  6. someList.MySort();

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

  • 只有静态
  • 不是多态的

C#中的函数式编程集成

C#为arity提供功能和程序通用委托预定义类型,最多16个:

  1. delegate TResult Func<TResult>();
  2. delegate TResult Func<T, TResult>(T a1);
  3. delegate TResult Func<T1, T2, TResult>(T1 a1, T2 a2);
  4. delegate void Action<T>(T a1);
  5. [ ... ]

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

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

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

因此,它们可以被存储和传输。

功能级别的代码抽象

一个简单的代码:

  1. float M(int y){
  2. int x1 = [ ... ];
  3. int x2 = [ ... ];
  4. [ ... ]
  5. [ ... some code ... ]; // some code using x1, x2 and y
  6. [ ... ]
  7. }

功能抽象:

  1. public delegate int Fun(int x, int y, int z);
  2. float MFun(Fun f, int x2, int y){
  3. int x1 = [ ... ];
  4. [ ... ]
  5. f(x1, x2, y);
  6. [ ... ]
  7. }
  8.  
  9. int z1 = MFun(F1, 1, 2);
  10. int z2 = MFun(F2, 1, 2);

功能抽象的优点是没有局部重复,并且存在关注点分离。

功能抽象的简单有效应用是对数据的通用高阶迭代操作。

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

  1. IEnumerable<T2> Map<T1, T2>(this IEnumerable<T1> data, Func<T1, T2> f){
  2. foreach(var x in data)
  3. yield return f(x);
  4. }
  5.  
  6. someList.Map(i => i * i);

运营组成

在功能编程中,操作组合物很容易。初始代码:

  1. public static void PrintWordCount(string s){
  2. string[] words = s.Split(' ');
  3.  
  4. for(int i = 0; i < words.Length; i++)
  5. words[i] = words[i].ToLower();
  6.  
  7. var dict = new Dictionary<string, int>();
  8.  
  9. foreach(var word in words)
  10. if (dict.ContainsKey(word))
  11. dict[word]++;
  12. else
  13.   dict.Add(word, 1);
  14.  
  15. foreach(var x in dict)
  16. Console.WriteLine("{0}: {1}", x.Key, x.Value.ToString());
  17. }

使用高阶函数的第一个因子

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

使用扩展方法的第二个因子:

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

我们可以看到代码的可读性增加了。

在C#中,这种操作组合通常与LINQ一起使用,LINQ被定义为将编程与关系数据或XML统一起来。下面是一个使用LINQ的简单示例:

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

功能部分应用和Currying

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

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

柯里:

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

面向对象编程中的体系结构功能编程技术

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

  1. 减少对象/类定义的数量。
  2. 在函数/方法级别命名抽象。
  3. 操作组合(和序列理解)。
  4. 功能部分应用和currying。

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

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

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

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

例如,战略设计模式。

战略

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

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

  1. public delegate int Comparison<T>(T x, T y);
  2. public void Sort(Comparison<T> comparison);
  3.  
  4. public delegate bool Predicate<T>(T obj);
  5. public List<T> FindAll(Predicate<T> match);

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

命令

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

  1. public delegate void EventHandler(object sender, EventArgs e);
  2. public event EventHandler Click;
  3.  
  4. private void menuItem1_Click(object sender, EventArgs e){
  5. OpenFileDialog fd = new OpenFileDialog();
  6. fd.DefaultExt = "*.*" ;
  7.   fd.ShowDialog();
  8. }
  9.  
  10. public void CreateMyMenu(){
  11. MainMenu mainMenu1 = new MainMenu();
  12. MenuItem menuItem1 = new MenuItem();
  13. [ ... ]
  14. menuItem1.Click += new EventHandler(menuItem1_Click);
  15. }

观察

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

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

  1. public interface Observer<S>{
  2. void Update(S s);
  3. }
  4.  
  5. public abstract class Subject<S>{
  6.   private List<Observer<S>> _observ = new List<Observer<S>>();
  7. public void Attach(Observer<S> obs){
  8. _observ.Add(obs);
  9. }
  10. public void Notify(S s){
  11. foreach (var obs in _observ)
  12. obs.Update(s);
  13. }
  14. }

功能编程:

  1. public delegate void UpdateFun<S>(S s);
  2.  
  3. public abstract class Subject<S>{
  4. private UpdateFun<S> _updateHandler;
  5.  
  6. public void Attach(UpdateFun<S> f){
  7. _updateHandler += f;
  8. }
  9. public void Notify(S s){
  10. _updateHandler(s);
  11. }
  12. }

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

虚拟代理

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

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

  1. public class SimpleProxy : I{
  2. private Simple _simple;
  3. private int _arg;
  4.  
  5. protected Simple GetSimple(){
  6. if (_simple == null)
  7. _simple = new Simple(_arg);
  8. return _simple;
  9. }
  10. public SimpleProxy(int i){
  11.   _arg = i ;
  12.   }
  13. public void Process(){
  14. GetSimple().Process();
  15. }
  16. }

下面使用函数式编程和懒惰实现虚拟代理设计模式:

  1. public class SimpleLazyProxy : I{
  2. private Lazy<Simple> _simpleLazy;
  3.  
  4. public SimpleLazyProxy(int i){
  5. _simpleLazy = new Lazy<Simple>(() => new Simple(i));
  6. }
  7. public void Process(){
  8. _simpleLazy.Value.Process();
  9. }
  10. }

游客

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

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

  1. public interface IFigure{
  2. string GetName();
  3. void Accept<T>(IFigureVisitor<T> v);
  4. }
  5.  
  6. public class SimpleFigure : IFigure{
  7. private string _name;
  8.  
  9. public SimpleFigure(string name){
  10.   _name = name;
  11.   }
  12. public string GetName(){
  13.   return _name;
  14.   }
  15. public void Accept<T>(IFigureVisitor<T> v){
  16. v.Visit(this);
  17. }
  18. }
  19.  
  20. public class CompositeFigure : IFigure{
  21. private string _name;
  22. private IFigure[] _figureArray;
  23.  
  24. public CompositeFigure(string name, IFigure[] s){
  25. _name = name;
  26.   _figureArray = s;
  27. }
  28. public string GetName(){
  29.   return _name;
  30.   }
  31. public void Accept<T>(IFigureVisitor<T> v){
  32. foreach (IFigure f in _figureArray)
  33. f.Accept (v);
  34. v.Visit(this);
  35. }
  36. }
  37.  
  38. public interface IFigureVisitor<T>{
  39. T GetVisitorState();
  40. void Visit(SimpleFigure f);
  41. void Visit(CompositeFigure f);
  42. }
  43.  
  44. public class NameFigureVisitor : IFigureVisitor<string>{
  45. private string _fullName = " ";
  46.  
  47. public string GetVisitorState(){
  48.   return _fullName;
  49.   }
  50. public void Visit(SimpleFigure f){
  51. _fullName += f.GetName() + " ";
  52. }
  53. public void Visit(CompositeFigure f){
  54. _fullName += f.GetName() + "/";
  55. }
  56. }

访客的一些众所周知的弱点:

  • 重构阻力:访客定义取决于其运行的客户端源码类集。
  • 静态:访问者的实现是静态的(类型安全但灵活性较低)。
  • 入侵:访问者需要客户类预期和/或参与选择正确的方法。
  • 命名不灵活:访问者需要同样命名访问方法的所有不同实现。

尝试使用扩展方法解决访问者问题:

  1. public interface IFigure{
  2. string GetName(); // no Accept method required
  3. }
  4. [ ... ]
  5.  
  6. public static class NameFigureVisitor{
  7. public static void NameVisit(this SimpleFigure f){
  8.   _state = f.GetName() + " " + _state;
  9.   }
  10. public static void NameVisit(this CompositeFigure f) {
  11. _fullName = f.GetName() + ":" + _fullName;
  12. foreach(IFigure g in f.GetFigureArray())
  13. g.NameVisit(); // dynamic dispatch required...
  14. [ ... ]
  15. }
  16. }

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

  1. public delegate T VisitorFun<V, T>(V f);
  2. public interface IFigureF{
  3. string GetName ();
  4. T Accept<T>(VisitorFun<IFigureF, T> v);
  5. }
  6.  
  7. public class SimpleFigureF : IFigureF{
  8. private string _name ;
  9.  
  10. public SimpleFigureF(string name){
  11.   _name = name ;
  12.   }
  13. public string GetName(){
  14.   return _name ;
  15.   }
  16. public T Accept<T>(VisitorFun<IFigureF, T> v){
  17. return v(this);
  18. }
  19. }
  20. [...]
  21.  
  22. public class CompositeFigureF : IFigureF{
  23. private string _name;
  24. private IFigureF[ ] _figureArray;
  25.  
  26. public CompositeFigureF(string name, IFigureF[] s){
  27. _name = name;
  28.   _figureArray = s;
  29. }
  30. public string GetName(){
  31.   return this._name;
  32.   }
  33. public T Accept<T>(VisitorFun<IFigureF, T> v){
  34. foreach(IFigureF f in _figureArray)
  35. f.Accept(v);
  36. return v(this);
  37. }
  38. }

以下简单功能访客:

  1. public static VisitorFun<IFigureF, string> MakeNameFigureVisitorFun(){
  2. string _state = "";
  3. return obj => {
  4. if(obj is SimpleFigureF)
  5. _state += obj.GetName() + " ";
  6. else if(obj is CompositeFigureF)
  7. _state += obj.GetName() + "/";
  8. return _state ;
  9. };
  10. }

以数据为导向的访问者:

  1. var dict1 = new Dictionary<Type, VisitorFun<IFigureF, string>>();
  2. dict1.Add(typeof(SimpleFigureF), f => f.GetName() + " ");
  3. dict1.Add(typeof(CompositeFigureF), f => f.GetName() + "/");
  4.  
  5. var nameFigureFunVisitor1 = MakeVisitorFun<IFigureF, string>(dict1);

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

加起来

具有函数式编程粒度级别的对象 - 节点编程:

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

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

C#中面向对象编程中的函数式编程详解的更多相关文章

  1. VMware 虚拟化编程(5) — VixDiskLib 虚拟磁盘库详解之一

    目录 目录 前文列表 VixDiskLib 虚拟磁盘库 虚拟磁盘数据的传输方式 Transport Methods VixDiskLib_ListTransportModes 枚举支持的传输模式 Vi ...

  2. Java程序设计(2021春)——第一章课后题(选择题+编程题)答案与详解

    Java程序设计(2021春)--第一章课后题(选择题+编程题)答案与详解 目录 Java程序设计(2021春)--第一章课后题(选择题+编程题)答案与详解 第一章选择题 1.1 Java与面向对象程 ...

  3. Java程序设计(2021春)——第二章课后题(选择题+编程题)答案与详解

    Java程序设计(2021春)--第二章课后题(选择题+编程题)答案与详解 目录 Java程序设计(2021春)--第二章课后题(选择题+编程题)答案与详解 第二章选择题 2.1 面向对象方法的特性 ...

  4. Python编程之列表操作实例详解【创建、使用、更新、删除】

    Python编程之列表操作实例详解[创建.使用.更新.删除] 这篇文章主要介绍了Python编程之列表操作,结合实例形式分析了Python列表的创建.使用.更新.删除等实现方法与相关操作技巧,需要的朋 ...

  5. VMware 虚拟化编程(7) — VixDiskLib 虚拟磁盘库详解之三

    目录 目录 前文列表 VixDiskLib 虚拟磁盘库 VixDiskLib_GetMetadataKeys VixDiskLib_ReadMetadata 获取虚拟磁盘元数据 VixDiskLib_ ...

  6. VMware 虚拟化编程(6) — VixDiskLib 虚拟磁盘库详解之二

    目录 目录 前文列表 VixDiskLib 虚拟磁盘库 VixDiskLib_Open 打开 VMDK File VixDiskLib_Read 读取 VMDK File 数据 VixDiskLib_ ...

  7. Python中生成器和yield语句的用法详解

    Python中生成器和yield语句的用法详解 在开始课程之前,我要求学生们填写一份调查表,这个调查表反映了它们对Python中一些概念的理解情况.一些话题("if/else控制流" ...

  8. Java程序设计(2021春)——第四章接口与多态课后题(选择题+编程题)答案与详解

    Java程序设计(2021春)--第四章接口与多态课后题(选择题+编程题)答案与详解 目录 Java程序设计(2021春)--第四章接口与多态课后题(选择题+编程题)答案与详解 第四章选择题 4.0 ...

  9. CSS中伪类及伪元素用法详解

    CSS中伪类及伪元素用法详解   伪类的分类及作用: 注:该表引自W3School教程 伪元素的分类及作用: 接下来让博主通过一些生动的实例(之前的作业或小作品)来说明几种常用伪类的用法和效果,其他的 ...

  10. SVN组成中trunk,branches and tags功能用法详解

    SVN组成中trunk,branches and tags功能用法详解  我相信初学开发在SVN作为版本管理时,都估计没可能考虑到如何灵活的运用SVN来管理开发代码的版本,下面我就摘录一篇文章来简单说 ...

随机推荐

  1. 用C实现OOP面向对象编程(1)

    如摘要所说,C语言不支持OOP(面向对象的编程).并这不意味着我们就不能对C进行面向对象的开发,只是过程要复杂许多.原来以C++的许多工作,在C语言中需我们手动去完成. 博主将与大家一起研究一下如下用 ...

  2. Python正则表达式进阶-零宽断言

    1. 什么是零宽断言 有时候在使用正则表达式做匹配的时候,我们希望匹配一个字符串,这个字符串的前面或后面需要是特定的内容,但我们又不想要前面或后面的这个特定的内容,这时候就需要零宽断言的帮助了.所谓零 ...

  3. linux-deployment

    官方 linux-deploymenthttp://doc.qt.io/qt-5/linux-deployment.html linuxdeployqthttps://github.com/probo ...

  4. 原生js封装轮播图

    个人实际开发中用到的效果问题总结出来便于自己以后开发查看调用,如果也适用其他人请随意拿走勿喷就行! 原生js对于思路要求比较高,在js代码我都写有备注,足够理解并使用,即使是小白或者刚入行的程序员也比 ...

  5. Django之分页器组件

    class Pagination(object): def __init__(self,current_page,all_count,per_page_num=2,pager_count=11): & ...

  6. Azkaban —— 编译及部署

    一.Azkaban 源码编译 1.1 下载并解压 Azkaban 在3.0版本之后就不提供对应的安装包,需要自己下载源码进行编译. 下载所需版本的源码,Azkaban的源码托管在GitHub上,地址为 ...

  7. spring boot 2.x 系列 —— actuator 服务监控与管理

    文章目录 一.概念综述 1.1 端点 1.2 启用端点 1.3 暴露端点 1.4 健康检查信息 二.项目说明 1.1 项目结构说明 1.2 主要依赖 1.3 项目配置 1.4 查看监控状态 三.自定义 ...

  8. vue随笔

    1.vue基础 Vue 是一个mvvm 的渐进式框架.Angular 是一个mvc的.所以vue的重点更偏向于mv 他的使用方式 大家会发现里面带有大量的$的属性. 学习vue的指令 V-for  用 ...

  9. 小范笔记:ASP.NET Core API 基础知识与Axios前端提交数据

    跟同事合作前后端分离项目,自己对 WebApi 的很多知识不够全,虽说不必要学全栈,可是也要了解基础知识,才能合理设计接口.API,方便与前端交接. 晚上回到宿舍后,对 WebApi 的知识查漏补缺, ...

  10. TypeScript算法与数据结构-数组篇

    数组是数据结构中最简单,也是使用最广泛的一种.在原生的js中,数组给我们提供了很多方便的操作方法,比如push(), pop(), shift(), unshift().但是出于对数据结构的学习,我们 ...