本文共 10204 字,大约阅读时间需要 34 分钟。
目录
使用函数式编程来丰富面向对象编程的想法是陈旧的。将函数编程函数添加到面向对象的语言中会带来面向对象编程设计的好处。
一些旧的和不太老的语言,具有函数式编程和面向对象的编程:
面向对象编程语言的实践包括函数编程技术的仿真:
函数编程和面向对象编程在不同的设计粒度级别上运行:
至少有两个问题:
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
C#delegate可能是匿名的:
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 IEnumerableMySort (this IEnumerable obj) where T:IComparable { [ ... ]}List someList = [ ... ];someList.MySort();
扩展方法在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);
函数抽象的优点是没有局部重复,并且存在关注点分离。
函数抽象的简单有效应用是对数据的泛型高阶迭代操作。
例如,内部迭代器(Map
s):
IEnumerableMap (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()); Dictionaryres = (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个一元函数的组合,即成为一个局部套用函数:
Funclam1 = (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
在面向对象编程中具有函数编程函数的一些架构效果:
为什么函数式编程通常集成到面向对象的编程中?
主要的面向对象编程语言基于类作为模块: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(Observerobs){ _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(UpdateFunf){ _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扩展方法的问题:
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 VisitorFunMakeNameFigureVisitorFun(){ 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/