サーチ…


前書き

デザインパターンは、ソフトウェア開発で頻繁に発生する問題に対する一般的な解決策です。以下は、コードの構造化と設計における標準化されたベストプラクティスのテンプレートと、これらのデザインパターンが適切である一般的なコンテキストの例です。

創造的なデザインパターンは、オブジェクトのインスタンス化を抽象化して、システムを作成、合成、および表現のプロセスからより独立させる。

シングルトン

シングルトンは、プログラム全体で共有されるクラスの単一のインスタンスで構成された、頻繁に使用されるデザインパターンです。

次の例では、 Fooクラスのインスタンスを保持するstaticプロパティを作成しstaticstaticプロパティはクラスのすべてのオブジェクト間で共有され、サブクラス化によって上書きすることはできません。

public class Foo
{
    static let shared = Foo()
    
    // Used for preventing the class from being instantiated directly
    private init() {}
    
    func doSomething()
    {
        print("Do something")
    }
}

使用法:

Foo.shared.doSomething()

privateイニシャライザは必ず覚えておいてください:

これにより、シングルトンが本当にユニークであることが保証され、アクセス制御のために外部オブジェクトがクラスの独自のインスタンスを作成できなくなります。すべてのオブジェクトにはSwiftのデフォルトのpublicイニシャライザが付属しているので、initをオーバーライドして非公開にする必要があります。 クラケンデフ

ファクトリメソッド

クラスベースのプログラミングでは、ファクトリメソッドパターンは、作成されるオブジェクトの正確なクラスを指定することなく、ファクトリメソッドを使用してオブジェクトを作成するという問題に対処するための作成パターンです。 Wikipediaのリファレンス

protocol SenderProtocol
{
    func send(package: AnyObject)
}

class Fedex: SenderProtocol
{
    func send(package: AnyObject)
    {
        print("Fedex deliver")
    }
}

class RegularPriorityMail: SenderProtocol
{
    func send(package: AnyObject)
    {
        print("Regular Priority Mail deliver")
    }
}

// This is our Factory
class DeliverFactory
{
    // It will be responsable for returning the proper instance that will handle the task
    static func makeSender(isLate isLate: Bool) -> SenderProtocol
    {
        return isLate ? Fedex() : RegularPriorityMail()
    }
}

// Usage:
let package = ["Item 1", "Item 2"]

// Fedex class will handle the delivery
DeliverFactory.makeSender(isLate:true).send(package)

// Regular Priority Mail class will handle the delivery
DeliverFactory.makeSender(isLate:false).send(package)

これを行うことで、クラスの実際の実装に依存せず、 sender()を誰がそれを消費しているか完全に透過的にします。

この場合、送信者が配信を処理し、 send()というメソッドを公開するだけで十分です。他にもいくつかの利点があります:クラスの結合を減らし、テストを簡単にし、誰がそれを消費しているのかを変更することなく、新しい動作を追加しやすくします。

オブジェクト指向設計において、インタフェースは、コードの概念的説明を容易にし、依存性を妨げるバリアを作成する抽象化の層を提供する。 Wikipediaのリファレンス

観察者

オブザーバパターンは、オブジェクトと呼ばれるオブジェクトがオブザーバと呼ばれる従属オブジェクトのリストを保持し、通常はそのメソッドの1つを呼び出すことによって、状態の変更を自動的に通知します。主に分散イベント処理システムの実装に使用されます。 Observerパターンは、使い慣れたModel-View-Controller(MVC)アーキテクチャーパターンの重要な部分です。 Wikipediaのリファレンス

基本的にオブザーバーパターンは、オブザーバーに特定の動作や状態の変化を通知できるオブジェクトがある場合に使用されます。

まず、通知センターのグローバル参照(クラス外)を作成します。

let notifCentre = NotificationCenter.default

今どこからでもこれを呼び出すことができますオブザーバーとしてクラスを登録したいと思っています...

notifCentre.addObserver(self, selector: #selector(self.myFunc), name: "myNotification", object: nil)

これは、クラスを "readForMyFunc"のオブザーバとして追加します。また、通知が受信されたときに関数myFuncを呼び出す必要があることを示します。この関数は、同じクラスで記述する必要があります。

func myFunc(){
    print("The notification has been received")
}

このパターンの利点の1つは、オブザーバーとして多くのクラスを追加できるため、通知後に多くのアクションを実行できることです。

この通知は、コード内のほぼどこからでも次の行で簡単に送信(または必要に応じて投稿)できます。

notifCentre.post(name: "myNotification", object: nil)

通知付きの情報を辞書として渡すこともできます

let myInfo = "pass this on"
notifCentre.post(name: "myNotification", object: ["moreInfo":myInfo])

しかし、関数に通知を追加する必要があります:

func myFunc(_ notification: Notification){
    let userInfo = (notification as NSNotification).userInfo as! [String: AnyObject]
    let passedInfo = userInfo["moreInfo"]
    print("The notification \(moreInfo) has been received")
    //prints - The notification pass this on has been received
}

責任の連鎖

オブジェクト指向設計では、責任連鎖パターンは、 commandオブジェクトのソースと一連のprocessingオブジェクトからなるデザインパターンです。各processingオブジェクトには、 processingできるコマンドオブジェクトのタイプを定義するロジックが含まれています。残りはチェーン内の次のprocessingオブジェクトに渡されます。このチェーンの最後に新しいprocessingオブジェクトを追加するメカニズムも存在します。 ウィキペディア

責任の連鎖を構成するクラスを設定する。

最初に、すべてのprocessingオブジェクトのインタフェースを作成します。

protocol PurchasePower {  
var allowable : Float { get }
  var role : String { get }
  var successor : PurchasePower? { get set }
}

extension PurchasePower {
  func process(request : PurchaseRequest){
    if request.amount < self.allowable {
      print(self.role + " will approve $ \(request.amount) for \(request.purpose)")
    } else if successor != nil {
      successor?.process(request: request)
    }
  }
}

次に、 commandオブジェクトを作成します。

struct PurchaseRequest {
  var amount : Float
  var purpose : String
}

最後に、責任の連鎖を構成するオブジェクトを作成します。

class ManagerPower : PurchasePower {
  var allowable: Float = 20
  var role : String = "Manager"
  var successor: PurchasePower?
}

class DirectorPower : PurchasePower {
  var allowable: Float = 100
  var role = "Director"
  var successor: PurchasePower?
}

class PresidentPower : PurchasePower {
  var allowable: Float = 5000
  var role = "President"
  var successor: PurchasePower?
}

一緒に開始して連鎖する:

let manager = ManagerPower()
let director = DirectorPower()
let president = PresidentPower()

manager.successor = director
director.successor = president

ここでオブジェクトを連鎖させるメカニズムは、プロパティへのアクセスです

実行するリクエストを作成する:

manager.process(request: PurchaseRequest(amount: 2, purpose: "buying a pen"))  // Manager will approve $ 2.0 for buying a pen
manager.process(request: PurchaseRequest(amount: 90, purpose: "buying a printer")) // Director will approve $ 90.0 for buying a printer

manager.process(request: PurchaseRequest(amount: 2000, purpose: "invest in stock")) // President will approve $ 2000.0 for invest in stock

イテレータ

コンピュータプログラミングにおいて、イテレータは、プログラマがコンテナ、特にリストを横切ることを可能にするオブジェクトである。 ウィキペディア

struct Turtle {
  let name: String
}

struct Turtles {
  let turtles: [Turtle]
}

struct TurtlesIterator: IteratorProtocol {
  private var current = 0
  private let turtles: [Turtle]

  init(turtles: [Turtle]) {
    self.turtles = turtles
  }

  mutating func next() -> Turtle? {
    defer { current += 1 }
    return turtles.count > current ? turtles[current] : nil
  }
}

extension Turtles: Sequence {
  func makeIterator() -> TurtlesIterator {
    return TurtlesIterator(turtles: turtles)
  }
}

そして、使用例は

let ninjaTurtles = Turtles(turtles: [Turtle(name: "Leo"),
                                     Turtle(name: "Mickey"),
                                     Turtle(name: "Raph"),
                                     Turtle(name: "Doney")])
print("Splinter and")
for turtle in ninjaTurtles {
  print("The great: \(turtle)")
}

ビルダーパターン

ビルダー・パターンは、 オブジェクト作成ソフトウェアの設計パターンです。抽象ファクトリパターンとファクトリメソッドパターンとは異なり、多態性を有効にすることを意図していますが、ビルダーパターンの意図は、入れ子構造のコンストラクタに対するパターンを解消することです。テレスコープ・コンストラクタのアンチ・パターンは、オブジェクト・コンストラクタのパラメータの組み合わせの増加が、指数関数的なコンストラクタ・リストにつながる場合に発生します。ビルダー・パターンは、多数のコンストラクターを使用する代わりに、それぞれの初期化パラメーターを段階的に受け取り、その結果生成されたオブジェクトを一度に戻すBuilderオブジェクトを使用します。

- ウィキペディア

ビルダー・パターンの主な目的は、オブジェクトの作成時のデフォルト構成をセットアップすることです。これは、オブジェクトが構築されることとそれを構築することに関連する他のすべてのオブジェクトとの間の仲介である。

例:

より明確にするために、 Car Builderの例を見てみましょう。

Carクラスには、以下のようなオブジェクトを作成するための多くのオプションが含まれていると考えてください。

  • 色。
  • 座席の数。
  • 車輪の数。
  • タイプ。
  • ギアタイプ。
  • モーター。
  • エアバッグの可用性。
import UIKit

enum CarType {
    case
    
    sportage,
    saloon
}

enum GearType {
    case
    
    manual,
    automatic
}

struct Motor {
    var id: String
    var name: String
    var model: String
    var numberOfCylinders: UInt8
}

class Car: CustomStringConvertible {
    var color: UIColor
    var numberOfSeats: UInt8
    var numberOfWheels: UInt8
    var type: CarType
    var gearType: GearType
    var motor: Motor
    var shouldHasAirbags: Bool
    
    var description: String {
        return "color: \(color)\nNumber of seats: \(numberOfSeats)\nNumber of Wheels: \(numberOfWheels)\n Type: \(gearType)\nMotor: \(motor)\nAirbag Availability: \(shouldHasAirbags)"
    }
    
    init(color: UIColor, numberOfSeats: UInt8, numberOfWheels: UInt8, type: CarType, gearType: GearType, motor: Motor, shouldHasAirbags: Bool) {
        
        self.color = color
        self.numberOfSeats = numberOfSeats
        self.numberOfWheels = numberOfWheels
        self.type = type
        self.gearType = gearType
        self.motor = motor
        self.shouldHasAirbags = shouldHasAirbags
        
    }
}

carオブジェクトの作成:

let aCar = Car(color: UIColor.black,
               numberOfSeats: 4,
               numberOfWheels: 4,
               type: .saloon,
               gearType: .automatic,
               motor: Motor(id: "101", name: "Super Motor",
                            model: "c4", numberOfCylinders: 6),
               shouldHasAirbags: true)

print(aCar)

/* Printing
 color: UIExtendedGrayColorSpace 0 1
 Number of seats: 4
 Number of Wheels: 4
 Type: automatic
 Motor: Motor(id: "101", name: "Super Motor", model: "c4", numberOfCylinders: 6)
 Airbag Availability: true
*/

この問題は、カーオブジェクトを作成するときに、多くの構成データを作成する必要があるということです。

Builderパターンを適用するには、初期化パラメータにデフォルト値設定し、必要に応じて変更する必要があります

CarBuilderクラス:

class CarBuilder {
    var color: UIColor = UIColor.black
    var numberOfSeats: UInt8 = 5
    var numberOfWheels: UInt8 = 4
    var type: CarType = .saloon
    var gearType: GearType = .automatic
    var motor: Motor = Motor(id: "111", name: "Default Motor",
                             model: "T9", numberOfCylinders: 4)
    var shouldHasAirbags: Bool = false
    
    func buildCar() -> Car {
        return Car(color: color, numberOfSeats: numberOfSeats, numberOfWheels: numberOfWheels, type: type, gearType: gearType, motor: motor, shouldHasAirbags: shouldHasAirbags)
    }
}

CarBuilderクラスは、作成されたcarオブジェクトの値を編集するために変更できるプロパティを定義します。

CarBuilderを使って新しい車を作りましょう:

var builder = CarBuilder()
// currently, the builder creates cars with default configuration.

let defaultCar = builder.buildCar()
//print(defaultCar.description)
/* prints
 color: UIExtendedGrayColorSpace 0 1
 Number of seats: 5
 Number of Wheels: 4
 Type: automatic
 Motor: Motor(id: "111", name: "Default Motor", model: "T9", numberOfCylinders: 4)
 Airbag Availability: false
*/

builder.shouldHasAirbags = true
// now, the builder creates cars with default configuration,
// but with a small edit on making the airbags available

let safeCar = builder.buildCar()
print(safeCar.description)
/* prints
 color: UIExtendedGrayColorSpace 0 1
 Number of seats: 5
 Number of Wheels: 4
 Type: automatic
 Motor: Motor(id: "111", name: "Default Motor", model: "T9", numberOfCylinders: 4)
 Airbag Availability: true
 */

builder.color = UIColor.purple
// now, the builder creates cars with default configuration
// with some extra features: the airbags are available and the color is purple

let femaleCar = builder.buildCar()
print(femaleCar)
/* prints
 color: UIExtendedSRGBColorSpace 0.5 0 0.5 1
 Number of seats: 5
 Number of Wheels: 4
 Type: automatic
 Motor: Motor(id: "111", name: "Default Motor", model: "T9", numberOfCylinders: 4)
 Airbag Availability: true
*/

Builderパターンを適用する利点は、デフォルト値を設定したり、これらのデフォルト値を変更したりすることにより、多くの構成を含むオブジェクトを簡単に作成できることです。

それを取るさらに:

良い習慣として、デフォルト値を必要とするすべてのプロパティは、クラス自体とそのビルダーによって実装される分離されたプロトコルでなければなりません。

この例にCarBluePrintという新しいプロトコルを作成しましょう:

import UIKit

enum CarType {
    case
    
    sportage,
    saloon
}

enum GearType {
    case
    
    manual,
    automatic
}

struct Motor {
    var id: String
    var name: String
    var model: String
    var numberOfCylinders: UInt8
}

protocol CarBluePrint {
    var color: UIColor { get set }
    var numberOfSeats: UInt8 { get set }
    var numberOfWheels: UInt8 { get set }
    var type: CarType { get set }
    var gearType: GearType { get set }
    var motor: Motor { get set }
    var shouldHasAirbags: Bool { get set }
}

class Car: CustomStringConvertible, CarBluePrint {
    var color: UIColor
    var numberOfSeats: UInt8
    var numberOfWheels: UInt8
    var type: CarType
    var gearType: GearType
    var motor: Motor
    var shouldHasAirbags: Bool
    
    var description: String {
        return "color: \(color)\nNumber of seats: \(numberOfSeats)\nNumber of Wheels: \(numberOfWheels)\n Type: \(gearType)\nMotor: \(motor)\nAirbag Availability: \(shouldHasAirbags)"
    }
    
    init(color: UIColor, numberOfSeats: UInt8, numberOfWheels: UInt8, type: CarType, gearType: GearType, motor: Motor, shouldHasAirbags: Bool) {
        
        self.color = color
        self.numberOfSeats = numberOfSeats
        self.numberOfWheels = numberOfWheels
        self.type = type
        self.gearType = gearType
        self.motor = motor
        self.shouldHasAirbags = shouldHasAirbags
        
    }
}

class CarBuilder: CarBluePrint {
    var color: UIColor = UIColor.black
    var numberOfSeats: UInt8 = 5
    var numberOfWheels: UInt8 = 4
    var type: CarType = .saloon
    var gearType: GearType = .automatic
    var motor: Motor = Motor(id: "111", name: "Default Motor",
                             model: "T9", numberOfCylinders: 4)
    var shouldHasAirbags: Bool = false
    
    func buildCar() -> Car {
        return Car(color: color, numberOfSeats: numberOfSeats, numberOfWheels: numberOfWheels, type: type, gearType: gearType, motor: motor, shouldHasAirbags: shouldHasAirbags)
    }
}

プロトコルにデフォルト値を必要とするプロパティを宣言することの利点は、新たに追加されたプロパティを実装することです。クラスがプロトコルに準拠する場合、クラスはすべてのプロパティ/メソッドを宣言しなければなりません。

「バッテリー名」という車を作成するための青写真に追加する必要がある新しい機能が必要であると考えてください。

protocol CarBluePrint {
    var color: UIColor { get set }
    var numberOfSeats: UInt8 { get set }
    var numberOfWheels: UInt8 { get set }
    var type: CarType { get set }
    var gearType: GearType { get set }
    var motor: Motor { get set }
    var shouldHasAirbags: Bool { get set }
    
    // adding the new property
    var batteryName: String { get set }
}

新しいプロパティを追加した後、2つのコンパイル時エラーが発生し、 CarBluePrintプロトコルに準拠していることが 'batteryName'プロパティを宣言する必要があることを通知します。これにより、 CarBuilderは、 batteryNameプロパティのデフォルト値を宣言して設定することが保証されます。

CarBluePrintプロトコルにbatteryName新しいプロパティを追加した後、 CarクラスとCarBuilderクラスの両方の実装は次のようになります。

class Car: CustomStringConvertible, CarBluePrint {
    var color: UIColor
    var numberOfSeats: UInt8
    var numberOfWheels: UInt8
    var type: CarType
    var gearType: GearType
    var motor: Motor
    var shouldHasAirbags: Bool
    var batteryName: String
    
    var description: String {
        return "color: \(color)\nNumber of seats: \(numberOfSeats)\nNumber of Wheels: \(numberOfWheels)\nType: \(gearType)\nMotor: \(motor)\nAirbag Availability: \(shouldHasAirbags)\nBattery Name: \(batteryName)"
    }
    
    init(color: UIColor, numberOfSeats: UInt8, numberOfWheels: UInt8, type: CarType, gearType: GearType, motor: Motor, shouldHasAirbags: Bool, batteryName: String) {
        
        self.color = color
        self.numberOfSeats = numberOfSeats
        self.numberOfWheels = numberOfWheels
        self.type = type
        self.gearType = gearType
        self.motor = motor
        self.shouldHasAirbags = shouldHasAirbags
        self.batteryName = batteryName
    }
}

class CarBuilder: CarBluePrint {
    var color: UIColor = UIColor.red
    var numberOfSeats: UInt8 = 5
    var numberOfWheels: UInt8 = 4
    var type: CarType = .saloon
    var gearType: GearType = .automatic
    var motor: Motor = Motor(id: "111", name: "Default Motor",
                             model: "T9", numberOfCylinders: 4)
    var shouldHasAirbags: Bool = false
    var batteryName: String = "Default Battery Name"
    
    func buildCar() -> Car {
        return Car(color: color, numberOfSeats: numberOfSeats, numberOfWheels: numberOfWheels, type: type, gearType: gearType, motor: motor, shouldHasAirbags: shouldHasAirbags, batteryName: batteryName)
    }
}

再度、 CarBuilderを使用して新しい車を作りましょう:

var builder = CarBuilder()

let defaultCar = builder.buildCar()
print(defaultCar)
/* prints
 color: UIExtendedSRGBColorSpace 1 0 0 1
 Number of seats: 5
 Number of Wheels: 4
 Type: automatic
 Motor: Motor(id: "111", name: "Default Motor", model: "T9", numberOfCylinders: 4)
 Airbag Availability: false
 Battery Name: Default Battery Name
*/

builder.batteryName = "New Battery Name"

let editedBatteryCar = builder.buildCar()
print(editedBatteryCar)
/*
 color: UIExtendedSRGBColorSpace 1 0 0 1
 Number of seats: 5
 Number of Wheels: 4
 Type: automatic
 Motor: Motor(id: "111", name: "Default Motor", model: "T9", numberOfCylinders: 4)
 Airbag Availability: false
 Battery Name: New Battery Name
 */


Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow