当前位置: 首页 > 科技观察

面向过程,面向对象,函数式思考同样问题的方法

时间:2023-03-21 22:24:00 科技观察

我之所以对函数式代码感兴趣,是因为函数式代码具有表现力,可以用短小精悍的代码来完成工作,同时时间可以针对具体问题提供优雅的解决方案。现代编程语言都在朝着面向对象、函数式、动态化、解释执行的方向发展,比如Ruby、Swift。其他语言更强调函数式编程,比如F#和Scala。这门语言拥有强大的类型推理系统,而且编写的代码整洁度惊人。在F#中写一个函数将两个数相加,在F#Interactive中输入:1letaddnum1num2=num1*num2;;F#Interactive为我们推断函数类型:valadd:num1:int->num2:int->int,这意味着add有两个int类型的参数,并获取1个int类型。作为参数的函数:123456//C#privateintTwice(ininput,Funcf){returnf(f(input));}varresult=Twice(2,n=>n*n);使用F#只需要一个非常简洁的函数声明:123456>lettwice(input:int)f=f(f(input));;valtwice:input:int->f:(int->int)->int>twice2(funn->n*n);;valit:int=16valtwice:input:int->f:(int->int)–>int这句话由F#InteractiveInference:thetwicefunctionrequiresanintparameteranda(int->int)functionas一个参数,并返回一个int。这两个例子只是预热,不是本博客的重点,所以你觉得前两个例子很无聊或者看不懂,请继续阅读下面的总结。场景:某个事件会有一个时间表(Schedule)。有3种类型的日程,分别是一次(Once)、一天一次(Daily)和一周一次(Weekly)。活动会根据档期类型有不同的宣传内容和不同的延期策略。你怎么看待这样的场景?1、面向过程的编码方式面向过程的编码是需求的直译过程,代码会这样写:1、展示活动的宣传内容:1234567891011121314151617publicvoidShowScheduleDescriptions(){switch(ScheduleType){caseScheduleType.Once:Console.WriteLine("这是一次活动");休息;caseScheduleType.Daily:Console.WriteLine("这是日常活动");休息;caseScheduleType.Weekly:控制台。WriteLine("这是每周活动");休息;默认值:thrownewInvalidOperationException(“不支持的计划”);}}这种代码乍一看还不错,但实际上有两个危险信号:违反了开放和封闭(OCP)原则,如果哪天需要添加一个Monthly类型,这个方法无疑需要修改;这种代码风格将使后续开发2.延迟活动:1234567891011121314151617publicvoidDelaySchedule(){switch(ScheduleType){caseScheduleType.一次:Console.WriteLine("延迟一小时");休息;caseScheduleType.Daily:Console.WriteLine("延迟一天");休息;caseScheduleType.Weekly:Console.WriteLine("延迟一周");:thrownewInvalidOperationException("不支持的schedule");}}这样的代码违反了DRY原则,但是同一个代码框架是不能复用的2、面向对象的编码方式对于一个有经验的OO开发者来说,一旦看到switch、if(type=typeof(...))这样的代码,就会立刻提高警惕。是不是还有一些抽象类型没有发现?在此示例中,将找到以下抽象:1234567891011121314151617181920212223242526272829303132333435363738publicclassSchedule{publicvirtualvoidShowShowScheduleDescriptions(){}publicvirtualvoidDelaySchedule(){}}publicclassOnceSchedulee:Schedule{publicoverridevoidShowShowScheduleDescriptions(){Console.WriteLine("这是一次活动");}publicoverridevoidDelaySchedule(){Console.WriteLine("延迟一小时");publicclassDailySchedule:时间表。“这是日常活动”);}publicoverridevoidDelaySchedule(){Console.WriteLine("Delaydailyday");}}//...otherschedule这种代码很好的解决了面向过程代码的两个问题,看它的可扩展性比较强。随着新类型Schedule的引入,旧代码根本不需要改动。当然,事情不是绝对的。什么情况下需要改旧代码?当需要扩展Schedule的行为时,比如需求升级,不同的Schedule有不同的hold方法,我们就得给每个Schedule加上一个voidHold()方法。3.函数式解决方案函数式语言使用可区分联合和模式匹配来处理此类问题。定义一个Schedule可区分联合:1234typeSchedule=|一次的日期时间|每日日期时间*int|WeeklyofDateTime*int这个类型不仅说明Schedule有三种不同的类型,还定义了这三种类型的数据结构。有点像Enum和class的结合,但是特别精致。1.显示活动的宣传内容,使用模式匹配:12345letShowShowScheduleDescriptionsschedule=matchschedulewith|Once(DateTime)->printfn"这是一次活动"|Daily(DateTime,int)->printfn"这是日常活动"|Weekly(DateTime,int)->printfn"thisisweeklyactivity"此方法类似于switch...case,但通过匹配可区分的联合而不是显式Enum来实现。2.延迟事件:12345letDelayScheduleschedule=matchschedulewith|Once(DateTime)->printfn"延迟一小时"|Daily(DateTime,int)->printfn"延迟一天"|Weekly(DateTime,int)->printfn"延迟一周"函数式编程的解决方案认为添加新行为很方便,比如添加新行为:Hold()。编码是通过定义可区分的联合和模式匹配来完成的。整个解决方案就像是面向过程和面向对象的结合,只是侧重点不同,实现的代码也更加细化。