做做Flutter-Day07:Dart in OOP之二

影山小麥機

--

聊聊Mixins、Enums、Extension Method、Callable objects

前言

最近還更新了一些配備,比如像是風鏡,以前風鏡的技術好像只能直接換鏡片,但現在的風鏡鍍膜技術似乎可以讓風鏡的鏡片在太陽照射的強度下變化,讓透明的鏡片變成是有護眼效果的,這樣的設計說真的還不錯R。

裝有近視內框的風鏡

不過因為我有近視,近視的人需要眼鏡,但如果要運動的話,不外乎就是三種途徑:

  1. 配隱形眼鏡
  2. 配近視內框
  3. 找比較貴的專業運動眼鏡商配鏡

最近採用第二個途徑來配鏡,但一配之下覺得OMG,完全沒想過我們平常的眼鏡配上有弧彎的近視內框會有聚焦的問題,然後眼睛的肌肉需要一直的調整焦距,接著就會有點暈暈的。

不過昨天實際戴近視內框踩上心之芳庭的時候,其實戴近視內框的感想是:「其實好像也不過就是習慣一下的事情」。但可能剛開始是真的需要習慣近視內框的內鏡是彎的。但如果要重來一次,我應該會直接買隱形眼鏡or買可以完整配到好的專業運動眼鏡。

正文

先來聊一個很神奇的設計叫做Mixin,這個語法的設計像是Swift中的protocol,如果還記得Protocol怎麼寫,我猜經驗會是圍繞在protocol的語法有點像額外添加屬性的概念,或是protocol在iOS開發中可以用來delegate物件,讓物件之間可以傳值之類的,又或者是大家耳熟能詳的POP。

Dart的Mixin其實就類似Protocol,不過在使用上有一些比較有趣的地方需要注意:

Mixin

以下會示範一個Mixin的使用範例:

我們可以看到我現在定義了兩個Mixin,一個是CanSwim、一個是CanFly,它們都是屬於一種形容詞類的特性,所以一旦加上這兩個屬性,那個被加上的物件就會有這個特質,那在下面的範例中,我們設計了兩種物件:Penguin、Duck,這兩種物件都會游泳,也都會飛,所以讓他們都有CanSwim、CanFly的特質應該不為過,所以誠如下面我們所寫的程式碼:

// Mixin: CanSwim
mixin CanSwim {
void swim() {
print('Swimming');
}
}

// Mixin: CanFly
mixin CanFly {
void fly() {
print('Flying');
}
}

// Class: Duck
class Duck with CanSwim, CanFly {
String name;

Duck(this.name);

void display() {
print('I am a duck named $name');
}
}

// Class: Penguin
class Penguin with CanSwim {
String name;

Penguin(this.name);

void display() {
print('I am a penguin named $name');
}
}

void main() {
var donaldDuck = Duck('Donald');
donaldDuck.display(); // I am a duck named Donald
donaldDuck.swim(); // Swimming
donaldDuck.fly(); // Flying

var tux = Penguin('Tux');
tux.display(); // I am a penguin named Tux
tux.swim(); // Swimming
}

這算是蠻像Protocol的設計方式,不過當然還有另類的特性在Mixin裡:

可以看到CanSwim這件事情擴充了Animal物件的特性,也就是說,其實Mixin是可以在使用上跟Class混搭的,這點在Swift中的Protocol中還沒看到過有人這麼使用:

class Animal {
void eat() {
print('Eating');
}
}

mixin CanSwim on Animal {
void swim() {
print('Swimming');
eat(); // 使用 Animal 的 eat 方法
}
}

class Dolphin extends Animal with CanSwim {
// ...
}

void main() {
var dolphin = Dolphin();
dolphin.swim();
}

所以,在創建dophin這個物件的時候,因為mixin的語法擴展了Animal的物件的特性,所以這邊的dophin可以連同CanSwim、Animal的function一起使用。

abstract mixin class

抽象mixin class其實要處理的事情還是之前帶有abstract物件是一樣的,會是可以定義未實踐的屬性、方法在裡面,然後由需要實踐的物件去依照這個抽象關係去定義在裡面設計的物件的內容,如下:

abstract class Animal {
void makeSound();
}

abstract mixin Swimmer on Animal {
void swim() {
print('Swimming');
}
}

class Dolphin extends Animal with Swimmer {
void makeSound() {
print('Ee-ee-ee');
}
}

class Penguin extends Animal {
void makeSound() {
print('Honk honk');
}
}

void main() {
Dolphin dolphin = Dolphin();
Penguin penguin = Penguin();

dolphin.makeSound(); // Ee-ee-ee
dolphin.swim(); // Swimming

penguin.makeSound(); // Honk honk
// penguin.swim(); // Error: Class 'Penguin' does not extend 'Swimmer'
}

Enum 枚舉

enum的用法其實大抵上也跟Swift類似,都是可以拿來跟switch case進行組合,可以像下面這樣寫:

enum Animal {
cat,
dog,
bird,
lion,
}

void main() {
Animal myAnimal = Animal.cat;

// 使用 switch/case 來根據動物類型進行不同的處理
switch (myAnimal) {
case Animal.cat:
print('這是一隻貓咪');
break;
case Animal.dog:
print('這是一隻狗狗');
break;
case Animal.bird:
print('這是一隻小鳥');
break;
case Animal.lion:
print('這是一隻獅子');
break;
default:
print('未知的動物');
}
}

不過,像下面這樣的舉例可以算是一個稍微比較複雜的enum用法,但都是要先定義要實作的物件,才可以將enum的分類的效果加進去程式碼裡面實作:

enum VehicleType {
car,
truck,
motorcycle,
bicycle,
}

class Vehicle {
final String name;
final VehicleType type;

Vehicle(this.name, this.type);
}

void main() {
Vehicle vehicle1 = Vehicle('Toyota Camry', VehicleType.car);
Vehicle vehicle2 = Vehicle('Ford F-150', VehicleType.truck);
Vehicle vehicle3 = Vehicle('Harley-Davidson', VehicleType.motorcycle);
Vehicle vehicle4 = Vehicle('Trek Mountain Bike', VehicleType.bicycle);

List<Vehicle> vehicles = [vehicle1, vehicle2, vehicle3, vehicle4];

for (var vehicle in vehicles) {
switch (vehicle.type) {
case VehicleType.car:
print('${vehicle.name} is a car');
break;
case VehicleType.truck:
print('${vehicle.name} is a truck');
break;
case VehicleType.motorcycle:
print('${vehicle.name} is a motorcycle');
break;
case VehicleType.bicycle:
print('${vehicle.name} is a bicycle');
break;
}
}
}

大致上,這個語法使用的情境都是在需要某種特定的分類的時候採用會讓開發比較容易閱讀。

extension擴展

這個用法跟Swift的extension其實也是大同小異,不過比較經典的語法差異大概如下:

最基本的extension呈現的樣貌:

extension <extension name>? on <type> {
(<member definition>)*
}

如果是要實踐擴展String這個型別的內容,可以寫自己定義的擴展名稱在需要擴展的型別或物件上:

extension NumberParsing on String {
int parseInt() {
return int.parse(this);
}

double parseDouble() {
return double.parse(this);
}
}

然後如果是沒有特定自訂的型別的擴展,可以像下面這樣寫:

extension on String {
bool get isBlank => trim().isEmpty;
}

那下面也簡單舉個實用的例子:

extension StringUtils on String {
int get wordCount {
return split(' ').length;
}

String capitalize() {
if (isEmpty) return '';
return this[0].toUpperCase() + substring(1);
}
}

void main() {
String sentence = 'hello world';
print(sentence.wordCount); // 輸出: 2
print(sentence.capitalize()); // 輸出: "Hello world"
}

我們就可以在void main()裡面看到實踐的擴展怎麼被String使用啦!

Callable objects

雖然我有點不知道為什麼要創建這個語法,不過它其實想要做出的效果是:

如何讓一個類的實例可以像函數一樣被調用的一種方法。通過實現call()方法,我們可以在該類的實例上使用函數調用的語法。

class WannabeFunction {
String call(String a, String b, String c) {
return '$a $b $c!';
}
}

void main() {
var myObject = WannabeFunction();
var result = myObject('Hello', 'world', 'Dart');
print(result); // Output: Hello world Dart!
}

可以實際的看main的function初始化物件之後,就可以實際的用String去做出call這個function的return type。

同樣的語法也可以用在初始化上:

class Multiplier {
int factor;

Multiplier(this.factor);

int call(int number) {
return number * factor;
}
}

void main() {
var doubler = Multiplier(2);
var result = doubler(5);
print(result); // Output: 10

var tripler = Multiplier(3);
result = tripler(5);
print(result); // Output: 15
}

所以看起來物件裡面的call這個關鍵字是不可以隨便亂設定的,看起來有可能會有奇特的化學反應呢。

後記

大概的看了一看Dart語法的物件導向,發現它並沒有像swift有struct的設計,反而在mixin上有蠻多性質的著墨的,所以大概這樣看下來,它一貫的設計邏輯都是class的,也就是reference type的,雖然這樣斷言有點怪,但我相信實際進到實作後,應該會有些不一樣的體悟。
大概物件導向的語法就先介紹到這邊,接下來還有兩個小章節:Concurrency、Null Safety,我這邊還會實際上的再花篇幅介紹一下XD

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

影山小麥機
影山小麥機

Written by 影山小麥機

本職為Mobile工程師,熱愛分享視野,也樂意站在ChatGPT的肩膀上。訂閱小麥機,收割技術、職涯、人生的難題。

No responses yet

Write a response