OOP 最自豪的就是 Polymorphism (多型),若使用 FP,該如何實現這個 OOP 的招牌菜呢?
Version
macOS High Sierra 10.13.3 .NET Core SDK 2.1.101 C# 7.2 F# 4.1
Shape Again
在眾多 OOP 書中,都會看到這個經典 Shape,這是展示 OOP 經典的 Polymorphism 與 Virtual / Override 的經典範例。
Circle、Rectangle 與 Triangle 都抽象化成 Shape 這個 abstract class
因為每個形狀計算面積的公式都不同,因此在 Shape 開 abstract method,再由 Circle 、 Rectangle 與 Triangle 各自實作 Area()
最後將 Circle、Rectangle 與 Triangle 都擺進 List,一一的呼叫每個形狀的 Area(),因為 OOP 的 Polymorphism 機制,所以我們不必去判斷 class 型別,自己會執行對的 class 與 method
這就是我們熟悉的 Polymorphism。
將分別以 C# 與 F# 實作此需求,體會一下 OOP 與 FP 的差異。
CSharp
Shape.cs
1 2 3 4 5 6 7 namespace ConsoleApp { public abstract class Shape { public abstract double Area ( ) ; } }
定義 Shape abstract class,其中 Area() 為 abstract method,因為每個形狀計算面積的公式不同,必須由子類別去實作。
Circle.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 namespace ConsoleApp { public class Circle : Shape { private readonly double _radius; public Circle (double radius ) { _radius = radius; } public override double Area ( ) { return 3.14 * _radius * _radius; } } }
Circle 由 constructor 傳入 radius,並 override Area() 計算圓形面積。
Rectangle.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 namespace ConsoleApp { public class Rectangle : Shape { private readonly double _width; private readonly double _height; public Rectangle (double width, double height ) { _width = width; _height = height; } public override double Area ( ) { return _width * _height; } } }
Rectangle 由 constructor 傳入 width 與 height,並 override Area() 計算矩形面積。
Triangle.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 namespace ConsoleApp { public class Triangle : Shape { private readonly double _base; private readonly double _height; public Triangle (double base_, double height ) { _base = base_; _height = height; } public override double Area ( ) { return 0.5 * _base * _height; } } }
Triangle 由 constructor 傳入 base 與 height,並 override Area() 計算三角形面積。
Program.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 using System;using System.Collections.Generic;namespace ConsoleApp { class Program { static void Main (string [] args ) { List<Shape> myList = new List<Shape> { new Circle(10.0 ), new Rectangle(2.0 , 3.0 ), new Triangle(4.0 , 5.0 ) }; foreach (var shape in myList) { var result = shape.Area(); Console.WriteLine("{0:F1}" , result); } } } }
將 Circle 、Rectangle 與 Triangle 3 個物件都塞入 List,使用 foreach 執行每個物件的 Area(),不用判斷型別,就自動會執行對的物件,這就是我們熟知的 Polymorphism。
各自算出各形狀的面積。
FSharp
Program.fs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 open Systemtype Shape = | Circle of radius: float | Rectangle of width: float * height: float | Triangle of base_: float * height: float let area shape = match shape with | Circle radius -> 3.14 * radius * radius | Rectangle (width, height) -> width * height | Triangle (base_, height) -> 0.5 * base_ * height [<EntryPoint>] let main argv = let circle = Circle 10.0 let rectangle = Rectangle (2.0 , 3.0 ) let triangle = Triangle (4.0 , 5.0 ) [circle; rectangle; triangle] |> List.map area |> List.iter (printfn "%A" ) 0
第 3 行
1 2 3 4 type Shape = | Circle of radius: float | Rectangle of width: float * height: float | Triangle of base_: float * height: float
定義 Shape union,包含 Circle、Rectangle 與 Triangle 三個 case。
至於三個型別的資料,則直接在 case 後面接 of 表示。
我們看到 C# 必須使用 abstract class 與 繼承,才能描述 Shape 與 Circle、Rectangle 與 Triangle 之間的抽象關係,但 F# 使用 union只要 4 行即可,而且還是強型別
第 8 行
1 2 3 4 5 let area shape = match shape with | Circle radius -> 3.14 * radius * radius | Rectangle (width, height) -> width * height | Triangle (base_, height) -> 0.5 * base_ * height
至於 Area() 的多型怎麼辦 ?
只要使用 Pattern Matching 判斷 shape 的型別,各自實作其面積公式即可。
我們看到 C# 必須使用 virtual / override 機制才能實現 Polymorphism,但 F# 使用 Pattern Matching 只要 5 行即可
15 行
1 2 3 4 5 6 7 8 let main argv = let circle = Circle 10.0 let rectangle = Rectangle (2.0 , 3.0 ) let triangle = Triangle (4.0 , 5.0 ) [circle; rectangle; triangle] |> List.map area |> List.iter (printfn "%A" )
不必使用 foreach ,只要以 Pipeline 方式透過 List.map 執行每個物件的 area(),再以 Pipeline 方式透過 List.iter 將計算的結果交給 printfn 顯示。
C# 雖然也可以使用 LINQ 方式,與 F# 極為類似,不過 LINQ 已經屬於 functional ,不是傳統 OOP 的 imperative
Conclusion
FP 使用 union 即可實作出 OOP 的 繼承 概念
FP 使用 Pattern Matching 即可實作出 OOP 的 virtual/override 概念
OOP 是將 data 與 function 合一,都包在 class 內;FP 是將 data 與 function 分離,data 歸 Type,function 還是 function
F# 的程式碼明顯比 C# 精簡
並不是要比較哪個語言的優劣,只想強調 OOP 與 FP 以不同的思維,都可以實作出 Polymorphism
Sample Code
完整的範例可以在我的 GitHub 上找到:C# 、F#
Reference
F# for fun and profit , Four Key Concepts