Swift Language
デザインパターン - 創造的
サーチ…
前書き
デザインパターンは、ソフトウェア開発で頻繁に発生する問題に対する一般的な解決策です。以下は、コードの構造化と設計における標準化されたベストプラクティスのテンプレートと、これらのデザインパターンが適切である一般的なコンテキストの例です。
創造的なデザインパターンは、オブジェクトのインスタンス化を抽象化して、システムを作成、合成、および表現のプロセスからより独立させる。
シングルトン
シングルトンは、プログラム全体で共有されるクラスの単一のインスタンスで構成された、頻繁に使用されるデザインパターンです。
次の例では、 Foo
クラスのインスタンスを保持するstatic
プロパティを作成しstatic
。 static
プロパティはクラスのすべてのオブジェクト間で共有され、サブクラス化によって上書きすることはできません。
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
*/