Emscripten の依存関係を含むプロジェクトのビルド

この投稿では、Emscripten を使用して複数のライブラリで構成されるプロジェクトを構築する方法について説明します。
現時点では、Emscripten は共有ライブラリの構築をサポートしていないため、最初のステップはすべてのライブラリを共有ライブラリから静的ライブラリに転送することです。 Emscripten は独自のインクルード ファイルを使用して動作するため、ヘッダー ファイルの可視性に関する問題は、システム ディレクトリから Emscripten ツールチェーンに転送することで解決する必要があります。

ln -s /usr/local/include/FlameSteelFramework $EMSDK/fastcomp/emscripten/system/include/FlameSteelFramework

CMake を使用している場合は、add_library メソッドの CMakeLists.txt ファイルで SHARED->STATIC を変更する必要があります。次のコマンドを使用して、さらに静的リンクを行うためのライブラリ/アプリケーションを構築できます。

emcmake cmake .
emmake make

次に、リンク段階で *.a ライブラリ ファイルを指定してメイン アプリケーションを構築する必要があります。相対パスを指定できませんでした。CMakeLists.txt ファイルで完全パスを指定した後でのみビルドが正しく完了しました。

elseif(EMSCRIPTEN)
target_link_libraries(${FSEGT_PROJECT_NAME} GL GLEW 
/home/demensdeum/Sources/cube-art-project-bootstrap/cube-art-project/sharedLib/libCubeArtProject.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelEngineGameToolkitFSGL/libFlameSteelEngineGameToolkitFSGL.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelEngineGameToolkit/libFlameSteelEngineGameToolkit.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelCore/libFlameSteelCore.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelBattleHorn/libFlameSteelBattleHorn.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FSGL/libFSGL.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelCommonTraits/libFlameSteelCommonTraits.a)
else()

ソース

https://emscripten.org/ docs/compiling/Building-Projects.html#using-libraries

共有ライブラリ CMake C++

私は最近、FlameSteelFramework のすべての部分を個別の共有ライブラリにすることにしました。次に、 FlameSteelCore:

cmake_minimum_required(VERSION 3.5)

project (FlameSteelCore)
set(CMAKE_BUILD_TYPE Release)

include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src)

file(GLOB_RECURSE SOURCE_FILES
    "src/FlameSteelCore/*.cpp"
)

add_library(FlameSteelCore SHARED ${SOURCE_FILES})

install(DIRECTORY "${CMAKE_SOURCE_DIR}/src/FlameSteelCore"
        DESTINATION include/FlameSteelFramework
        FILES_MATCHING
        PATTERN "*.h"
)

install(TARGETS FlameSteelCore DESTINATION lib)

CMake が実行するコマンド: *.cpp 拡張子を持つすべてのファイルを src/FlameSteelCore/ ディレクトリから共有ライブラリに収集し、*.h 拡張子を持つすべてのヘッダーを src/FlameSteelCore から include/FlameSteelFramework にコピーします (私の場合)これは /usr/local/include/FlameSteelFramework)、共有ライブラリを lib ディレクトリにコピーします(/usr/local/lib)
インストール後、LD キャッシュの更新が必要になる場合があります。 sudo ldconfig。
Ubuntu でビルドしてインストールするには (正しいビルド ツールチェーンがある場合)、次のコマンドを実行するだけです。

cmake . && make && sudo make install

インストール プロセスをテストするために、make プレフィックスをローカル フォルダー makeInstallTestPlayground に渡します。

cmake -DCMAKE_INSTALL_PREFIX:PATH=/home/demensdeum/makeInstallTestPlayground . && make && make install

参考文献

https: //stackoverflow.com/questions/17511496/how-to-create-a-shared-library-with-cmake
https://stackoverflow.com/questions/6003374/what-is-cmake-equivalent-of-configure-prefix-dir-make-all-install

C++ 言語インタプリタ –しがみつく

つい最近、Cling という興味深いプロジェクトに出会いました。これは、コンソールなどから対話的に動作できる C++ 言語インタープリタです。プロジェクトは次のリンクで表示できます: https://github.com/root -プロジェクト/ しがみつく
Ubuntu のインストールは簡単です。必要なバージョンのアーカイブをダウンロードして解凍し、bin フォルダーに移動してターミナルで cling を実行します。
以下は、ライブラリ FlameSteelCore をロードし、オブジェクトを初期化し、ID を出力する例です。

失われた Emscripten 例外と正規表現の問題

例外の紛失

Emscripten の興味深い機能: emscripten_set_main_loop 経由でゲーム ループを開始するときは、ループ メソッド内で直接 try catch 経由で例外処理を再追加する必要があることに注意してください。ランタイムは外部からのトライキャッチブロックに負け
ます。最も簡単な方法は、ブラウザで JavaScript アラートを使用してエラー テキストを表示することです。

            catch (const std::exception &exc)
            {
                const char *errorText = exc.what();
                cout << "Exception: " << errorText << "; Stop execution" << endl;

                EM_ASM_(
                {
                    var errorText = UTF8ToString($0);
                    alert(errorText);

                }, errorText);

                abort();

正規表現が複雑すぎます

正規表現の標準実装は、正規表現が複雑すぎると判断した場合、error_complexity 例外をスローすることがあります。これは emscripten の現在の実装で発生するため、正規表現による解析のテストを実装するか、サードパーティの正規表現実装を使用することをお勧めします。

パターンビルダー

Builder パターンは、私にとってその存在が特に明確ではないパターンのグループに属していますが、これが明らかに冗長であることに注目します。ジェネレーティブ デザイン パターンのグループに属します。複雑なオブジェクトを作成するための単純なインターフェースを実装するために使用されます。

適用性

インターフェースの簡素化。 多数の引数を持つコンストラクターでのオブジェクトの作成が容易になり、コードの可読性が客観的に向上します。

ビルダーを使用しない C++ の例:

auto weapon = new Weapon(“Claws”);
monster->weapon = weapon;
auto health = new MonsterHealth(100);
monster->health = health;

Пример со строителем на C++:

                  .addWeapon(“Claws”)
                  .addHealth(100)
                  .build();

Однако в языках поддерживающих именованные аргументы (named arguments), необходимость использовать именно для этого случая отпадает.

Пример на Swift с использованием named arguments:

let monster = Monster(weapon: “Claws”, health: 100)

不変性。 ビルダーを使用すると、最終アセンブリ段階まで作成されたオブジェクトを確実にカプセル化できます。ここで、パターンを使用することで作業環境の激しい変動から身を守ることができるかどうかを慎重に考える必要があります。おそらく、開発チーム内にカプセル化を使用する文化が欠如しているため、パターンを使用しても何も得られません。 .

オブジェクト作成のさまざまな段階でのコンポーネントとの対話。 また、このパターンを使用すると、システムの他のコンポーネントと対話するときに、オブジェクトを段階的に作成することができます。おそらくこれは非常に便利です (?)

批判

もちろん、プロジェクト内でこのパターンの広範な使用を確立する価値があるかどうかを*慎重に*考える必要があります。最新の構文と高度な IDE を備えた言語では、コードの読みやすさの向上という点で、ビルダーを使用する必要がなくなります (名前付き引数に関するポイントを参照)。
GoF 本が出版された 1994 年にこのパターンを使用すべきだったでしょうか?おそらくその通りですが、当時のオープン ソース コード ベースから判断すると、それを使用している人はほとんどいませんでした。

ソース

https://refactoring.guru/ru/design-patterns/builder

パターンコンポジット

複合パターンは構造設計パターンを指し、国内情報源では「コンポジター」として知られています。
アプリケーションを開発しているとします。写真アルバム。ユーザーはフォルダーを作成し、そこに写真を追加したり、その他の操作を実行したりできます。フォルダー内のファイルの数、すべてのファイルとフォルダーの合計数を表示する機能は必ず必要です。
ツリーを使用する必要があることは明らかですが、シンプルで便利なインターフェイスを備えたツリー アーキテクチャを実装するにはどうすればよいでしょうか?複合パターンが役に立ちます。

ムーンダックスを着たシーラ

次に、ディレクトリに dataCount() メソッドを実装します –コンポーネントの配列にあるすべての要素を調べて、それらの dataCount をすべて合計します。
準備は完了です!
以下は Go の例です。
package main

import "fmt"

type component interface {

dataCount() int

}

type file struct {

}

type directory struct {

c []component

}

func (f file) dataCount() int {

return 1

}

func (d directory) dataCount() int {

var outputDataCount int = 0

for _, v := range d.c {
outputDataCount += v.dataCount()
}

return outputDataCount

}

func (d *directory) addComponent(c component) {

d.c = append(d.c, c)

}

func main() {

var f file
var rd directory
rd.addComponent(f)
rd.addComponent(f)
rd.addComponent(f)
rd.addComponent(f)

fmt.Println(rd.dataCount())

var sd directory
sd.addComponent(f)

rd.addComponent(sd)
rd.addComponent(sd)
rd.addComponent(sd)

fmt.Println(sd.dataCount())
fmt.Println(rd.dataCount())

}

ソース

https://refactoring.guru/ru/design-patterns/複合

パターンアダプター

ベンハミン・ヌニェス・ゴンサレス

アダプター パターンは、構造設計パターンを指します。

アダプターは、2 つのクラス/インターフェイス間のデータ/インターフェイス変換を提供します。

ニューラル ネットワークに基づいて店舗での購入者の目標を決定するシステムを開発しているとします。このシステムは店舗のカメラからビデオ ストリームを受信し、顧客の行動によって顧客を識別し、グループに分類します。グループの種類 –買いに来た(買い手候補)、ただ見るだけ(見物人)、何かを盗みに来た(泥棒)、商品を返しに来た(不満を持った買い手)、酔ってハイになった(乱暴者になる可能性)。

すべての経験豊富な開発者と同様に、私たちは、ベルリン動物園の動物研究所のご厚意により無償で公開された、ビデオ ストリームに基づいて檻の中のサルの種を分類できる既製のニューラル ネットワークを見つけ、ビデオ ストリームで再トレーニングしました。ストアから購入して、動作する最先端のシステムを入手してください。

ちょっとした問題があります –ビデオ ストリームは mpeg2 形式でエンコードされており、システムは OGG Theora のみをサポートしています。私たちはシステムのソース コードを持っていません。私たちにできる唯一のことは – です。データセットを変更してニューラル ネットワークをトレーニングします。何をするか? mpeg2 -> OGG Theora からストリームを転送し、ニューラル ネットワークに送信するアダプター クラスを作成します。

古典的なスキームによれば、パターンにはクライアント、ターゲット、アダプティ、アダプタが含まれます。この場合のクライアントは、OGG Theora でビデオ ストリームを受信するニューラル ネットワークであり、ターゲットは – です。対話するインターフェース、adaptee – mpeg2 でビデオ ストリームを送信するインターフェイス、アダプター – mpeg2 を OGG Theora に変換し、ターゲット インターフェイス経由で送信します。

すべてが簡単に思えますか?

ソース

https://ru.wikipedia.org/wiki/Adapter_ (デザインパターン)
https://refactoring.guru/ru/design-patterns/adapter

デリゲートパターン

デリゲート パターンは、主要なデザイン パターンの 1 つです。
理髪店アプリケーションを開発しているとします。アプリケーションには記録する日を選択するためのカレンダーがあり、日付をタップすると床屋のリストが開き、選択肢が表示されます。
システム コンポーネントの単純なリンクを実装し、相互へのポインタを使用してカレンダーと画面を結合し、リスト表示を実装しましょう。


// псевдокод

class BarbershopScreen {
   let calendar: Calendar

   func showBarbersList(date: Date) {
      showSelectionSheet(barbers(forDate: date))
   }
}

class Calendar {
    let screen: BarbershopScreen

    func handleTap(on date: Date) {
        screen.showBarbersList(date: date)
    }
}

数日後、要件が変更されます。リストを表示する前に、サービス (ひげのトリミングなど) を選択したオファーを表示する必要があります。ただし、土曜日を除くすべての日に表示される必要はありません。
カレンダーに土曜日かどうかのチェックを追加し、それに応じて理容師のリストまたはサービスのリストのメソッドを呼び出します。わかりやすくするために、次に示します。


// псевдокод

class BarbershopScreen {
   let calendar: Calendar

   func showBarbersList(date: Date) {
      showSelectionSheet(barbers(forDate: date))
   }

   func showOffersList() {
      showSelectionSheet(offers)
   }
}

class Calendar {
    let screen: BarbershopScreen

    func handleTap(on date: Date)  {
        if date.day != .saturday {
             screen.showOffersList()
        }
        else {
             screen.showBarbersList(date: date)
        }
    }
}

1 週間後、フィードバック画面にカレンダーを追加するように求められました。その瞬間、最初のアーキテクチャ上の問題が発生しました。
何をするか?カレンダーはカットの予約画面としっかりと連動
しています。おお!うーん!ああああ
このクレイジーなアプリケーション アーキテクチャで作業を続ける場合は、カレンダー クラス全体のコピーを作成し、このコピーをフィードバック画面に関連付ける必要があります。
よし、良さそうだ。さらに画面をいくつか追加し、カレンダーのコピーをいくつか追加したところ、X の瞬間が来た。カレンダーのデザインを変更するように求められました。つまり、カレンダーのすべてのコピーを見つけて、すべてに同じ変更を追加する必要があります。この「アプローチ」は開発のスピードに大きく影響し、間違いを犯す可能性も高まります。その結果、そのようなプロジェクトは最終的に壊れた状態になり、元のアーキテクチャの作成者ですらクラスのコピーがどのように機能するかを理解できなくなり、途中で追加された他のハックがその場で崩壊してしまいます。
何をしなければならなかったのか、あるいはもっと良く言えば、何を始めるのに遅すぎなかったのか?委任パターンを使用してください。
委任は、共通のインターフェイスを通じてクラス イベントを渡す方法です。以下は、カレンダーのデリゲートの例です。

protocol CalendarDelegate {
   func calendar(_ calendar: Calendar, didSelect date: Date)
}

次に、デリゲートを操作するためのコードをサンプル コードに追加しましょう。


// псевдокод

class BarbershopScreen: CalendarDelegate {
   let calendar: Calendar

   init() {
       calendar.delegate = self
   }

   func calendar(_ calendar: Calendar, didSelect date: Date) {
        if date.day != .saturday {
            showOffersList()
        }
        else {
             showBarbersList(date: date)
        }
   }

   func showBarbersList(date: Date) {
      showSelectionSheet(barbers(forDate: date))
   }

   func showOffersList() {
      showSelectionSheet(offers)
   }
}

class Calendar {
    weak var delegate: CalendarDelegate

    func handleTap(on date: Date)  {
        delegate?.calendar(self, didSelect: date)
    }
}

その結果、カレンダーから日付を選択するときにカレンダーを画面から完全に解放し、日付選択イベント – を送信します。イベント処理をサブスクライバに *委任*します。加入者は画面
です。このアプローチからどのようなメリットが得られるのでしょうか?クラスを複製することなく、カレンダーと画面のロジックを互いに独立して変更できるようになり、さらなるサポートが簡素化されます。このようにして、システムコンポーネントの実装に対する「単独責任の原則」が実装され、DRY 原則が遵守されます。
委任を使用すると、ウィンドウを表示するためのロジックや画面上の順序を追加したり変更したりできます。これは、客観的には直接関係のないプロセスに参加すべきではないカレンダーや他のクラスにはまったく影響しません。< br />あるいは、あまり気にしないプログラマは、別個のプロトコル/デリゲート インターフェイスを作成せずに、共通バス経由でメッセージを送信することを使用します。この場合、デリゲーションを使用する方が良いでしょう。このアプローチの欠点については、以前の投稿で書きました。 「オブザーバーパターン」

ソース

https://refactoring.guru/ru/replace-inheritance -委任付き

デスマスク1.0!

本日、Flame Steel Engine、Flame Steel Engine Game Toolkit、その他のライブラリとともにゼロから作成された、ゲーム Death-Mask のリリース バージョンが公開されました。

https://demensdeum.com/games/deathMask/< /p>

このゲームでは、あなたはレビル レイザーバックという名前の男の役割を果たします。彼は無限の命を与えるアーティファクトを手に入れたいと考えています –死のマスク。探して歩き回っていると、探しているものが見つかるまで、何度もドローンの手によって命を落とすことになります。

近い将来、3D モデルの更新やゲームプレイの小さな変更が行われる可能性があります。

デスマスク –無限のテクノラビリンスを舞台にしたサイバー ファンタジー アドベンチャー。死ぬ準備をしてください!

オブザーバーパターン

Observer パターンは、動作設計パターンを指します。
このパターンを使用すると、共通のインターフェイスを使用してオブジェクトの状態の変更をサブスクライバーに送信できます。
プログラマー向けのメッセンジャーを開発していて、アプリケーションにチャット画面があるとします。 「問題」と「エラー」または「何かがおかしい」という文字のメッセージが表示された場合は、エラー一覧画面や設定画面を赤色にする
必要があります。次に、問題を解決するための 2 つのオプションについて説明します。1 つ目はシンプルですがサポートが非常に困難で、2 つ目はサポートがはるかに安定していますが、最初の実装では頭を切り替える必要があります。

共通バス

パターンのすべての実装には、データ変更時のメッセージの送信、メッセージのサブスクライブ、メソッドでのさらなる処理が含まれています。共有バス オプションには、受信者にメッセージをディスパッチする単一のオブジェクト (通常はシングルトン) が含まれています。
実装の簡単さは次のとおりです。

<オル>

  • オブジェクトは抽象メッセージを共有バスに送信します
  • 共有バスに登録されている別のオブジェクトがメッセージをキャッチし、それを処理するかどうかを決定します。
  • Apple から利用可能な実装オプションの 1 つ (NSNotificationCenter サブシステム) では、配信時に受信者によって呼び出されるメソッドの名前とメッセージ ヘッダーの一致が追加されました。
    このアプローチの最大の欠点は、–メッセージをさらに変更する場合は、まずメッセージが処理および送信されるすべての場所を記憶し、手動で編集する必要があります。迅速な初期実装の後に、正しく動作するためのナレッジ ベースを必要とする長く複雑なサポートが続くケースもあります。

    マルチキャスト デリゲート

    この実装では、最終的なマルチキャスト デリゲート クラスを作成します。共有バスの場合と同様に、オブジェクトはこれをサブスクライブして「メッセージ」または「イベント」を受信できますが、メッセージの解析とフィルタリングの作業は次のとおりです。オブジェクトに割り当てられていません。代わりに、サブスクライバ クラスは、デリゲートに通知するためのデリゲートのマルチキャスト メソッドを実装する必要があります。
    これは、デリゲート インターフェイス/プロトコルを使用して実装されます。一般的なインターフェイスが変更されると、アプリケーションは構築されなくなり、その時点で、別のナレッジ ベースを維持することなく、特定のメッセージを処理するためにすべての場所をやり直す必要があります。これらの場所を思い出すために。 コンパイラはあなたの友達です。
    このアプローチでは、ドキュメントを書いたり保存したりする必要がなく、新しい開発者がメッセージとその引数がどのように処理されるかを理解しようとする必要がなく、代わりに便利でわかりやすいインターフェイスで作業できるため、チームの生産性が向上します。これは、コードによるドキュメント パラダイムが実装される方法です。
    >マルチキャスト デリゲート自体はデリゲート パターンに基づいています。これについては次の投稿で説明します。

    ソース

    https://refactoring.gu/ru/design-patterns/observer

    プロキシパターン

    プロキシ パターンは、構造設計パターンを指します。
    このパターンは、クラス層を通じてクラスを操作するテクニックを説明します。プロキシ。プロキシを使用すると、元のクラスのインターフェイスを維持しながら、元の動作を維持しながら、元のクラスの機能を変更できます。
    状況を想像してみましょう – 2015年、西ヨーロッパのある国は、統計を改善し、国民の政治的感情を深く理解するために、その国のユーザーのWebサイトへのすべてのリクエストを記録することを決定しました。
    市民がインターネットにアクセスするために使用するゲートウェイの単純な実装の擬似コードを想像してみましょう。

    class InternetRouter {
    
        private let internet: Internet
    
        init(internet: Internet) {
            self.internet = internet
        }
    
        func handle(request: Request, from client: Client) -> Data {
            return self.internet.handle(request)
        }
    
    }
    

    上記のコードでは、インターネット アクセスを提供するオブジェクトへのポインターを含むインターネット ルーター クラスを作成します。クライアントが Web サイトにリクエストを送信すると、インターネットから応答が返されます。

    プロキシ パターンとシングルトン アンチパターンを使用して、クライアント名と URL をログに記録する機能を追加します。

    class InternetRouterProxy {
    
        private let internetRouter: InternetRouter
    
        init(internet: Internet) {
            self.internetRouter = InternetRouter(internet: internet)
        }
    
        func handle(request: Request, from client: Client) -> Data {
    
            Logger.shared.log(“Client name: \(client.name), requested URL: \(request.URL)”)
    
            return self.internetRouter.handle(request: request, from: client)
        }
    
    }
    

    プロキシ クラス InternetRouterProxy には元の InternetRouter インターフェイスが保存されているため、初期化クラスを InternerRouter からそのプロキシに置き換えるだけで十分であり、コード ベースをさらに変更する必要はありません。

    ソース

    https://refactoring.guru/ru/design-patterns/プロキシ

    デスマスク ワイルド ベータ版

    ゲーム Death-Mask がパブリック ベータ ステータス (ワイルド ベータ) に入ります
    ゲームのメイン メニュー画面が再設計され、心地よい音楽をバックにテクノラビリンスのブルー ゾーンのビューが追加されました。

    次に、ゲームプレイ コントローラーを再加工し、古いシューティング ゲームのようなスムーズな動き、ボックス、武器、敵の高品質 3D モデル、ポータルだけでなくテクノラビリンスの他のレベルに移動できる機能を追加する予定です (エレベーター、ドア、床の穴、壁の穴などの落下など、生成される迷宮の環境に少し変化を加えていきます。ゲームバランスについても
    取り組んでいきます。スケルトン アニメーションは、リリース前の磨き段階として追加されます。< /p>

    パターンのプロトタイプ

    プロトタイプ パターンは、ジェネレーティブ デザイン パターンのグループに属します。
    私たちが出会い系アプリ Tender を開発しているとします。私たちのビジネス モデルによれば、有料で自分のプロフィールのコピーを作成し、名前や写真の順序を自動的に変更できます。これは、ユーザーがアプリケーション内でさまざまな友達のグループと複数のプロフィールを同時に管理できるようにするために行われました。
    ボタンをクリックしてプロフィールのコピーを作成することで、プロフィールのコピー、名前の自動生成、写真の再並べ替えを実装する必要があります。
    単純な擬似コードの実装:

    fun didPressOnCopyProfileButton() {
        let profileCopy = new Profile()
        profileCopy.name = generateRandomName()
        profileCopy.age = profile.age
        profileCopy.photos = profile.photos.randomize()
        storage.save(profileCopy)
    }
    

    次に、他のチーム メンバーがコピー コードをコピーアンドペーストしたり、最初からコードを考え出したりした後、新しいフィールドが追加されたと想像してみましょう。好きです。このフィールドにはプロフィールの「いいね!」の数が保存されます。ここで、新しいフィールドを追加して、コピーが行われる *すべて* の場所を手動で更新する必要があります。コードの保守とテストは非常に時間がかかり、困難です。
    この問題を解決するために、プロトタイプ設計パターンが発明されました。必要なフィールドを含むオブジェクトのコピーを返す copy() メソッドを使用して、一般的なコピー プロトコルを作成しましょう。エンティティ フィールドを変更した後は、コピー コードを含むすべての場所を手動で検索して更新するのではなく、copy() メソッドを 1 つ更新するだけで済みます。

    ソース

    https://refactoring.guru/ru/design-patterns/prototype

    ステートマシンとパターンの条件

    この記事では、ステート マシン (State Machine) の使用法について説明し、簡単な実装、State パターンを使用した実装を示します。状態が 3 つ未満の場合、State パターンを使用するのは望ましくないことに注意してください。これにより通常、コードの可読性が不必要に複雑になり、関連するサポートの問題が発生します。すべては適度に行う必要があります。

    MEAACT PHOTO / STUART PRICE.

    ロードオブフラッグス

    民間航空機のメディア システム用のビデオ プレーヤー画面を開発しているとします。プレーヤーは、ビデオ ストリームをロードして再生し、ユーザーがダウンロード プロセスの停止、巻き戻し、その他の通常の操作を実行できる必要があります。選手
    です。プレーヤーがビデオ ストリームの次のチャンクをキャッシュし、再生に十分なチャンクがあることを確認し、ユーザーに対してフラグメントの再生を開始し、同時に次のチャンクのダウンロードを継続したとします。
    この時点で、ユーザーはビデオの途中まで巻き戻します。つまり、現在のフラグメントの再生を停止し、新しい位置から読み込みを開始する必要があります。ただし、これを実行できない状況もあります。ユーザーは、航空安全に関するビデオが表示されている間、ビデオ ストリームの再生を制御できません。この状況を確認するために isSafetyVideoPlaying フラグを確認してみましょう
    。また、システムは現在のビデオを一時停止し、プレーヤーを通じて船長と乗組員からの警告をブロードキャストできなければなりません。別の is NoticePlaying フラグを追加しましょう。さらに、プレーヤーの操作に関するヘルプを表示している間は再生を一時停止しないという要件があります。別のフラグはHelpPresentingです。

    メディア プレーヤーの疑似コードの例:

    class MediaPlayer {
    
        public var isHelpPresenting = false
        public var isCaching = false
        public var isMediaPlaying: Bool = false
        public var isAnnouncementPlaying = false
        public var isSafetyVideoPlaying = false
    
        public var currentMedia: Media = null
    
        fun play(media: Media) {
    
            if isMediaPlaying == false, isAnnouncementPlaying == false, isSafetyVideoPlaying == false {
    
                if isCaching == false {
                    if isHelpPresenting == false {
                        media.playAfterHelpClosed()
                    }
                    else {
                        media.playAfterCaching()
                    }
                }
        }
    
        fun pause() {
            if isAnnouncementPlaying == false, isSafetyVideoPlaying == false {
                currentMedia.pause()
            }
        }
    }
    

    上記の例は、変動性 (エントロピー) が大きいため読みにくく、保守も困難です。この例は、ステート マシンを使用していない *多く* のプロジェクトのコード ベースを扱った私の経験に基づいています。
    各チェックボックスは、アプリケーションのインターフェイスとビジネス ロジックの要素を明確に「制御」する必要があります。開発者は、別のチェックボックスを追加して、可能なすべてのオプションを使用してすべてを数回確認し、再確認することでそれらを調整できる必要があります。
    「2 ^ チェックボックスの数」の式を代入すると、わずか 6 つのチェックボックスに対して 2 ^ 6 = 64 個のアプリケーション動作のオプションが得られます。これらのチェックボックスの組み合わせはすべて手動でチェックして維持する必要があります。
    開発者側から見ると、このようなシステムに新しい機能を追加すると次のようになります。
    –航空会社のブラウザ ページを表示する機能を追加する必要があります。乗務員が何かをアナウンスする場合、映画と同様に最小限に抑える必要があります。
    –わかりました、やります。 (くそー、別のフラグを追加して、フラグが交差するすべての場所を再確認する必要があります。変更する必要があるものがたくさんあります!)

    これもフラグ システムの弱点です –アプリケーションの動作に変更を加える。 1 つのフラグだけを変更した後にすべてを再確認する必要がある場合、フラグに基づいて動作を迅速かつ柔軟に変更する方法を想像するのは非常に困難です。この開発アプローチは多くの問題を引き起こし、時間とお金の損失につながります。

    マシンに入る

    フラグをよく見ると、実際には現実世界で発生する特定のプロセスを処理しようとしていることがわかります。通常モード、安全ビデオの表示、船長や乗組員からのメッセージのブロードキャストなどを列挙します。プロセスごとに、アプリケーションの動作を変更する一連のルールがわかっています。
    ステート マシン (ステート マシン) パターンのルールに従って、すべてのプロセスを列挙型の状態としてリストし、プレーヤー コードに状態としての概念を追加し、フラグの組み合わせを削除することで状態ベースの動作を実装します。このようにして、テストのオプションを状態の数と同じ数まで減らします。

    疑似コード:

    enum MediaPlayerState {
    	mediaPlaying,
    	mediaCaching,
    	crewSpeaking,
    	safetyVideoPlaying,
    	presentingHelp
    }
    
    class MediaPlayer {
    	fun play(media: Media) {
    		media.play()
    	}
    
    	func pause() {
    		media.pause()
    	}
    }
    
    class MediaPlayerStateMachine {
    	public state: MediaPlayerState
    	public mediaPlayer: MediaPlayer
    	public currentMedia: Media
    
    	//.. init (mediaPlayer) etc
    
    	public fun set(state: MediaPlayerState) {
    		switch state {
    			case mediaPlaying:
    				mediaPlayer.play(currentMedia)
    			case mediaCaching, crewSpeaking,
    			safetyVideoPlaying, presentingHelp:
    				mediaPlayer.pause()
    		}
    	}
    }
    

    フラグ システムとステート マシンの大きな違いは、set(state: ..) メソッドの論理状態切り替えファネルです。これにより、ロジックを実行することなく、人間による状態の理解をプログラム コードに変換できます。コードがさらにサポートされると、フラグを状態に変換するゲーム。

    パターンの状態

    次に、ステート マシンの単純な実装とステート パターンの違いを示します。 10 個の状態を追加する必要があると想像してください。その結果、ステート マシン クラスはゴッドオブジェクトのサイズまで大きくなり、維持が困難になり、コストが高くなります。もちろん、この実装はフラグ実装よりも優れています (フラグ システムを使用すると、開発者は最初に自分自身を撃ちます。そうでない場合は、2 ^ 10 = 1024 のバリエーションを見て、QA は首を吊るでしょう。ただし、両方とも * 気付かなかった場合) * タスクが複雑な場合、アプリケーションが単純なユーザーは、特定のフラグの組み合わせでの作業を拒否することに気づくでしょう)
    州の数が多い場合は州パターンを使用する必要があります。
    一連のルールを State プロトコルに追加しましょう。

    protocol State {
        func playMedia(media: Media, context: MediaPlayerContext)
        func shouldCacheMedia(context: MediaPlayerContext)
        func crewSpeaking(context: MediaPlayerContext)
        func safetyVideoPlaying(context:MediaPlayerContext)
        func presentHelp(context: MediaPlayerContext)
    }
    

    一連のルールの実装を別の状態に移動してみましょう。たとえば、1 つの状態のコードです。

    class CrewSpeakingState: State {
    	func playMedia(context: MediaPlayerContext) {
    		showWarning(“Can’ t play media - listen to announce!”)
    	}
    
    	func mediaCaching(context: MediaPlayerContext) {
    		showActivityIndicator()
    	}
    
    	func crewSpeaking(context: MediaPlayerContext) {
    		set(volume: 100)
    	}
    
    	func safetyVideoPlaying(context: MediaPlayerContext) {
    		set(volume: 100)
    	}
    
    	func presentHelp(context: MediaPlayerContext) {
    		showWarning(“Can’ t present help - listen to announce!”)
    	}
    }
    

    次に、各状態が機能するコンテキストを作成し、状態マシンを統合しましょう。

    final class MediaPlayerContext {
    	private
    	var state: State
    
    	public fun set(state: State) {
    		self.state = state
    	}
    
    	public fun play(media: Media) {
    		state.play(media: media, context: this)
    	}
    
    	…
    	остальные возможные события
    }
    

    アプリケーション コンポーネントはパブリック メソッドを通じてコン​​テキストと連携し、コンテキスト内のステート マシンを使用してどの状態からどの状態に遷移するかを決定します。
    したがって、God Object 分解を実装しました。プロトコルの変更を追跡するコンパイラーのおかげで、変化する状態の維持がはるかに簡単になり、コード行数の削減により状態を理解する複雑さが軽減され、次の点に焦点が当てられます。特定の状態の問題を解決します。また、1 つの大規模なステート マシン クラスを操作するときに発生する競合を「解決」する必要性を心配することなく、チーム内で作業を共有し、特定の状態の実装をチーム メンバーに提供できるようになりました。

    ソース

    https://refactoring.guru/ru/design-patterns/state

    スケルトン アニメーション (パート 1 – シェーダー)

    この記事では、キャラクターやゲーム環境などをアニメーション化するために最新のすべての 3D エンジンで使用されているスケルトン アニメーションについての私の理解を説明します。
    最も具体的な部分から説明を始めます –なぜなら、計算パス全体は、それがどれほど複雑であっても、表示用に準備されたデータを頂点シェーダーに転送することで終了するからです。

    スケルトン アニメーションは、CPU で処理された後、頂点シェーダーに入ります。
    スケルトン アニメーションを使用しない頂点の公式を思い出させてください。
    gl_Position = 投影マトリックス * ビューマトリックス * モデルマトリックス * 頂点;
    この式がどのように生まれたのか理解できない人は、OpenGL のコンテキストで 3D コンテンツを表示するための行列の操作原理を説明した私の記事を読んでください。
    残りについては –スケルトン アニメーションを実装するための公式:
    ” vec4 アニメーション頂点 = ボーン 0 マトリックス * 頂点 * ボーン 0 ウェイト +”
    “ボーン 1 マトリックス * 頂点 * ボーン 1 ウェイト +”
    “ボーン 2 マトリックス * 頂点 * ボーン 2 ウェイト +”
    “bone3matrix * 頂点 *bone3weight;\n”
    ” gl_Position = projectMatrix * viewMatrix * modelMatrix * animeVertex;\n”

    つまり、最終的なボーン変換行列に頂点と、頂点に対するこの行列の重みを乗算します。各頂点は 4 つのボーンでアニメーション化でき、衝撃の強さはボーンの重量パラメータによって制御され、衝撃の合計は 1 に等しくなる必要があります。
    頂点に影響を与えるボーンが 4 つ未満の場合はどうすればよいですか?重みを分担して残りの影響をゼロにする必要があります
    。数学的には、重みと行列を乗算することを「行列のスカラー乗算」と呼びます。スカラーを乗算すると、結果の頂点に対する行列の効果を合計できます。

    ボーン変換行列自体は配列として送信されます。 さらに、配列には各メッシュの行列ではなく、モデル全体の行列が含まれています。

    ただし、頂点ごとに次の情報が個別に送信されます。
    –頂点に影響を与える行列のインデックス
    です。–頂点に影響を与える行列の重み
    です。複数のボーンが送信され、通常は頂点の 4 つのボーンの効果が使用されます。
    また、4つのサイコロの重みの合計は必ず1に
    ならなければなりません。次にシェーダーでの見え方を
    見てみましょう。行列配列:
    “均一マット 4 ボーン行列[kMaxBones];”

    各頂点の 4 つのボーンの効果に関する情報:
    “属性 vec2bone0info;”
    “属性 vec2bone1info;”
    “属性 vec2bone2info;”
    “属性 vec2bone3info;”

    vec2 – X 座標にはボーンのインデックスを保存し (シェーダーで int に変換します)、Y 座標には頂点に対するボーンの影響の重みを保存します。なぜこのデータを 2 次元ベクトルで送信する必要があるのでしょうか? GLSL は、有効なフィールドを持つ C 読み取り可能な構造体をシェーダーに渡すことをサポートしていないためです。

    以下に、animatedVertex 式にさらに代入するために必要な情報をベクトルから取得する例を示します。

    “intbone0Index = int(bone0info.x);”
    “floatbone0weight =bone0info.y;”
    “mat4bone0matrix =bonesMatrices[bone0Index];”

    “intbone1Index = int(bone1info.x);”
    “floatbone1weight =bone1info.y;”
    “mat4bone1matrix =boneMatrices[bone1Index];”

    “intbone2Index = int(bone2info.x);”
    “floatbone2weight =bone2info.y;”
    “mat4bone2matrix =bonesMatrices[bone2Index];”

    “intbone3Index = int(bone3info.x);”
    “floatbone3weight =bone3info.y;”
    “mat4bone3matrix =bonesMatrices[bone3Index];”

    これで、CPU に設定された頂点構造は次のようになります。
    x、y、z、u、v、bone0index、bone0weight、bone1index、bone1weight、bone2index、bone2weight、bone3index、bone3weight

    頂点バッファ構造はモデルの読み込み中に一度埋められますが、変換マトリックスは各レンダリング フレームで CPU からシェーダーに転送されます。

    残りの部分では、CPU 上でアニメーションを計算する原理を説明します。アニメーションを頂点シェーダーに転送する前に、アニメーション、モデル、ノード、メッシュ階層、マトリックスをたどるボーン ノードのツリーについて説明します。補間。

    ソース

    http://ogldev.atspace.co.英国/www/tutorial38/tutorial38.html

    ソースコード

    https://gitlab.com/demensdeum/skeletal-animation

    テンプレートメソッド

    パターン手法とは、動作設計パターンを指します。このパターンは、クラスのロジックの一部をオンデマンドで置き換え、全体の部分は子孫に対して変更しないままにする方法を説明します。

    Cuban Cars

    クライアント銀行を開発していると仮定して、承認モジュールを開発するタスクを考えてみましょう –ユーザーは抽象ログイン データを使用してアプリケーションにログインできる必要があります。
    認可モジュールはクロスプラットフォームである必要があり、さまざまな認可テクノロジをサポートし、さまざまなプラットフォームの暗号化されたデータを保存する必要があります。モジュールを実装するには、クロスプラットフォーム Kotlin 言語を選択し、認可モジュールの抽象クラス (プロトコル) を使用して、MyPhone 電話の実装を作成します。

    class MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage {
        fun loginAndPassword() : Pair {
            return Pair("admin", "qwerty65435")
        }
    }
    
    class ServerApiClient {
        fun authorize(authorizationData: AuthorizationData) : Unit {
            println(authorizationData.login)
            println(authorizationData.password)
            println("Authorized")
        }
    }
    
    class AuthorizationData {
        var login: String? = null
        var password: String? = null
    }
    
    interface AuthorizationModule {
        abstract fun fetchAuthorizationData() : AuthorizationData
        abstract fun authorize(authorizationData: AuthorizationData)
    }
    
    class MyPhoneAuthorizationModule: AuthorizationModule {
        
        override fun fetchAuthorizationData() : AuthorizationData {
            val loginAndPassword = MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage().loginAndPassword()
            val authorizationData = AuthorizationData()
            authorizationData.login = loginAndPassword.first
            authorizationData.password = loginAndPassword.second
            
            return authorizationData
        }
        
        override fun authorize(authorizationData: AuthorizationData) {
            ServerApiClient().authorize(authorizationData)
        }
        
    }
    
    fun main() {
        val authorizationModule = MyPhoneAuthorizationModule()
        val authorizationData = authorizationModule.fetchAuthorizationData()
        authorizationModule.authorize(authorizationData)
    }
    

    ここで、電話/プラットフォームごとに、サーバーに認証を送信するためのコードを複製する必要があります。これは DRY 原則に違反します。上記の例は非常に単純ですが、より複雑なクラスではさらに多くの重複が発生します。コードの重複を排除するには、テンプレート メソッド パターンを使用する必要があります。
    モジュールの共通部分を不変メソッドに移動し、暗号化されたデータ転送の機能を特定のプラットフォーム クラスに転送しましょう。

    class MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage {
        fun loginAndPassword() : Pair {
            return Pair("admin", "qwerty65435")
        }
    }
    
    class ServerApiClient {
        fun authorize(authorizationData: AuthorizationData) : Unit {
            println(authorizationData.login)
            println(authorizationData.password)
            println("Authorized")
        }
    }
    
    class AuthorizationData {
        var login: String? = null
        var password: String? = null
    }
    
    interface AuthorizationModule {
        abstract fun fetchAuthorizationData() : AuthorizationData
        
        fun authorize(authorizationData: AuthorizationData) {
            ServerApiClient().authorize(authorizationData)
        }
    }
    
    class MyPhoneAuthorizationModule: AuthorizationModule {
        
        override fun fetchAuthorizationData() : AuthorizationData {
            val loginAndPassword = MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage().loginAndPassword()
            val authorizationData = AuthorizationData()
            authorizationData.login = loginAndPassword.first
            authorizationData.password = loginAndPassword.second
            
            return authorizationData
        }
        
    }
    
    fun main() {
        val authorizationModule = MyPhoneAuthorizationModule()
        val authorizationData = authorizationModule.fetchAuthorizationData()
        authorizationModule.authorize(authorizationData)
    }
    

    ソース

    https://refactoring.guru/ru/design-パターン/テンプレート メソッド

    ソースコード

    https://gitlab.com/demensdeum/patterns/< /p>

    パターンブリッジ

    ブリッジ パターンは、構造設計パターンを指します。ロジックを別の抽象クラスに移動することで、クラス ロジックの実装を抽象化できます。簡単そうですよね?

    さまざまな種類のメッセンジャーにメッセージを送信できるスパム ボットを実装するとします。
    共通のプロトコルを使用して実装します。

    protocol User {
        let token: String
        let username: String
    }
    
    protocol Messenger {
        var authorize(login: String, password: String)
        var send(message: String, to user: User)
    }
    
    class iSeekUUser: User {
        let token: String
        let username: String
    }
    
    class iSeekU: Messenger {
    
        var authorizedUser: User?
        var requestSender: RequestSender?
        var requestFactory: RequestFactory?
    
        func authorize(login: String, password: String) {
            authorizedUser = requestSender?.perform(requestFactory.loginRequest(login: login, password: password))
        }
        
        func send(message: String, to user: User) {
            requestSender?.perform(requestFactory.messageRequest(message: message, to: user)
        }
    }
    
    class SpamBot {
        func start(usersList: [User]) {
            let iSeekUMessenger = iSeekU()
            iSeekUMessenger.authorize(login: "SpamBot", password: "SpamPassword")
            
            for user in usersList {
                iSeekUMessennger.send(message: "Hey checkout demensdeum blog! http://demensdeum.com", to: user)
            }
        }
    }
    

    次に、iSekU メッセンジャーのメッセージを送信するための、より高速な新しいプロトコルのリリースを想像してみましょう。新しいプロトコルを追加するには、iSekU ボットの実装を複製し、その一部のみを変更する必要があります。クラスロジックのほんの一部が変更されただけの場合、なぜこれを行うのかは不明です。このアプローチでは、DRY 原則に違反します。製品がさらに開発されると、新機能の実装におけるエラーや遅延によって柔軟性の欠如が顕著になります。
    プロトコルのロジックを抽象クラスに移動して、Bridge パターンを実装しましょう。

    protocol User {
        let token: String
        let username: String
    }
    
    protocol Messenger {
        var authorize(login: String, password: String)
        var send(message: String, to user: User)
    }
    
    protocol MessagesSender {
        func send(message: String, to user: User)
    }
    
    class iSeekUUser: User {
        let token: String
        let username: String
    }
    
    class iSeekUFastMessengerSender: MessagesSender {
        func send(message: String, to user: User) {
            requestSender?.perform(requestFactory.messageRequest(message: message, to: user)
        }
    }
    
    class iSeekU: Messenger {
    
        var authorizedUser: User?
        var requestSender: RequestSender?
        var requestFactory: RequestFactory?
        var messagesSender: MessengerMessagesSender?
    
        func authorize(login: String, password: String) {
            authorizedUser = requestSender?.perform(requestFactory.loginRequest(login: login, password: password))
        }
        
        func send(message: String, to user: User) {
            messagesSender?.send(message: message, to: user)
        }
    }
    
    class SpamBot {
    
        var messagesSender: MessagesSender?
    
        func start(usersList: [User]) {
            let iSeekUMessenger = iSeekU()
            iSeekUMessenger.authorize(login: "SpamBot", password: "SpamPassword")
            
            for user in usersList {
                messagesSender.send(message: "Hey checkout demensdeum blog! http://demensdeum.com", to: user)
            }
        }
    }
    

    このアプローチの利点の 1 つは、メイン アプリケーションのコードを変更せずに、抽象化されたロジックを実装するプラグイン/ライブラリを作成することで、アプリケーションの機能を拡張できることです。
    戦略パターンとの違いは何ですか?どちらのパターンも非常に似ていますが、Strategy では *アルゴリズム* の切り替えについて説明しますが、Bridge では *任意の複雑なロジック* の大部分を切り替えることができます。

    ソース

    https://refactoring.guru/ru/design-patterns/bridge

    ソースコード

    https://gitlab.com/demensdeum/patterns/p>

    責任連鎖パターン

    責任の連鎖とは、行動設計パターンを指します。


    ガンナ・ドルビエワ

    映画会社 Jah-Pictures は、リベリアの共産主義者ラスタファリアンについてのドキュメンタリー映画「Red Dawn of Marley」を製作しました。この映画は非常に長く(8時間)、興味深いものですが、公開前に、一部の国では映画のショットやフレーズが異端とみなされ、配給ライセンスが与えられないことが判明しました。映画のプロデューサーは、手動および自動で、疑わしいフレーズを含む瞬間を映画から切り取ることにしました。一部の国では、手動による検査と設置中にエラーが発生した場合に販売代理店の担当者が単純に射殺されないよう、二重のチェックが必要です。
    国は 4 つのグループに分けられます。検閲のない国、中程度、中程度、非常に厳しい検閲がある国。ニューラル ネットワークを使用して、視聴された映画の断片の異端のレベルを分類することが決定されました。このプロジェクトでは、非常に高価な最先端のニューロンが購入され、開発者の任務であるさまざまなレベルの検閲に合わせてトレーニングされます。フィルムを断片に分割し、フリーから厳密まで一連のニューラル ネットワークを介して送信し、そのうちの 1 つが異端を検出した後、断片は手動レビュー用に転送され、さらなる編集が行われます。すべてのニューロンを通過することは不可能です。彼らの作業には多大なコンピューティング能力が必要です (結局のところ、電気代を支払わなければなりません)。最初に動作するもので停止すれば十分です。
    単純な擬似コードの実装:

    import StateOfArtCensorshipHLNNClassifiers
    
    protocol MovieCensorshipClassifier {
        func shouldBeCensored(movieChunk: MovieChunk) -> Bool
    }
    
    class CensorshipClassifier: MovieCensorshipClassifier {
    
        let hnnclassifier: StateOfArtCensorshipHLNNClassifier
    
        init(_ hnnclassifier: StateOfArtCensorshipHLNNClassifier) {
            self.hnnclassifier = hnnclassifier
        }
        
        func shouldBeCensored(_ movieChunk: MovieChunk) -> Bool {
            return hnnclassifier.shouldBeCensored(movieChunk)
        }
    }
    
    let lightCensorshipClassifier = CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("light"))
    let normalCensorshipClassifier = CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("normal"))
    let hardCensorshipClassifier = CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("hard"))
    
    let classifiers = [lightCensorshipClassifier, normalCensorshipClassifier, hardCensorshipClassifier]
    
    let movie = Movie("Red Jah rising")
    for chunk in movie.chunks {
        for classifier in classifiers {
            if classifier.shouldBeCensored(chunk) == true {
                print("Should censor movie chunk: \(chunk), reported by \(classifier)")
            }
       }
    }
    

    一般に、分類子の配列を使用したソリューションはそれほど悪くはありませんが、配列を作成できず、映画の断片の検閲の種類をすでに決定している分類子エンティティを 1 つだけ作成する機会があると想像してみましょう。アプリケーション(プラグイン)の機能を拡張するライブラリを開発する場合、このような制限が
    発生する可能性があります。デコレータ パターン – を使用してみましょう。チェーン内の次の分類子への参照を分類子クラスに追加し、最初に分類が成功した時点で検証プロセスを停止しましょう。
    したがって、責任連鎖パターンを実装します。

    import StateOfArtCensorshipHLNNClassifiers
    
    protocol MovieCensorshipClassifier {
        func shouldBeCensored(movieChunk: MovieChunk) -> Bool
    }
    
    class CensorshipClassifier: MovieCensorshipClassifier {
    
        let nextClassifier: CensorshipClassifier?
        let hnnclassifier: StateOfArtCensorshipHLNNClassifier
    
        init(_ hnnclassifier: StateOfArtCensorshipHLNNClassifier, nextClassifier: CensorshipClassifiers?) {
                self.nextClassifier = nextClassifier
                self.hnnclassifier = hnnclassifier
        }
        
        func shouldBeCensored(_ movieChunk: MovieChunk) -> Bool {
            let result = hnnclassifier.shouldBeCensored(movieChunk)
            
            print("Should censor movie chunk: \(movieChunk), reported by \(self)")
            
            if result == true {
                    return true
            }
            else {
                    return nextClassifier?.shouldBeCensored(movieChunk) ?? false
            }
        }
    }
    
    let censorshipClassifier = CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("light"), nextClassifier: CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("normal", nextClassifier: CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("hard")))))
    
    let movie = Movie("Red Jah rising")
    for chunk in movie.chunks {
        censorshipClassifier.shouldBeCensored(chunk)
    }
    

    参考文献

    https://refactoring.guru/ru/デザインパターン/責任連鎖

    ソースコード

    https://gitlab.com/demensdeum/patterns/< /p>

    パターンデコレーター

    Decorator パターンは、構造設計パターンを指します。

    デコレータは、クラスの機能を拡張するための継承の代替として使用されます。
    製品の種類に応じてアプリケーションの機能を拡張するという課題があります。顧客は 3 種類の製品を必要としています。ベーシック、プロフェッショナル、究極。
    基本–文字数をカウントする、Professional –機能 Basic + テキストを大文字で印刷します。Ultimate – Basic + Professional + 究極というテキストを印刷します。
    継承を使用して実装します。

    protocol Feature {
    	func textOperation(text: String)
    }
    
    class BasicVersionFeature: Feature {
    	func textOperation(text: String) {
    		print("\(text.count)")
    	}
    }
    
    class ProfessionalVersionFeature: BasicVersionFeature {
    	override func textOperation(text: String) {
    		super.textOperation(text: text)
    		print("\(text.uppercased())")
    	}
    }
    
    class UltimateVersionFeature: ProfessionalVersionFeature {
    	override func textOperation(text: String) {
    		super.textOperation(text: text)
    		print("ULTIMATE: \(text)")
    	}
    }
    
    let textToFormat = "Hello Decorator"
    
    let basicProduct = BasicVersionFeature()
    basicProduct.textOperation(text: textToFormat)
    
    let professionalProduct = ProfessionalVersionFeature()
    professionalProduct.textOperation(text: textToFormat)
    
    let ultimateProduct = UltimateVersionFeature()
    ultimateProduct.textOperation(text: textToFormat)
    

    ここで、製品「Ultimate Light」– を実装する必要があります。 Basic + Ultimate ですが、Professional バージョンの機能はありません。最初の OH! が起こるからです。このような単純なタスク用に別のクラスを作成し、コードを複製する必要があります。
    継承を使用して実装を続けましょう:

    protocol Feature {
    	func textOperation(text: String)
    }
    
    class BasicVersionFeature: Feature {
    	func textOperation(text: String) {
    		print("\(text.count)")
    	}
    }
    
    class ProfessionalVersionFeature: BasicVersionFeature {
    	override func textOperation(text: String) {
    		super.textOperation(text: text)
    		print("\(text.uppercased())")
    	}
    }
    
    class UltimateVersionFeature: ProfessionalVersionFeature {
    	override func textOperation(text: String) {
    		super.textOperation(text: text)
    		print("ULTIMATE: \(text)")
    	}
    }
    
    class UltimateLightVersionFeature: BasicVersionFeature {
    	override func textOperation(text: String) {
    		super.textOperation(text: text)
    		print("ULTIMATE: \(text)")	
    	}
    }
    
    let textToFormat = "Hello Decorator"
    
    let basicProduct = BasicVersionFeature()
    basicProduct.textOperation(text: textToFormat)
    
    let professionalProduct = ProfessionalVersionFeature()
    professionalProduct.textOperation(text: textToFormat)
    
    let ultimateProduct = UltimateVersionFeature()
    ultimateProduct.textOperation(text: textToFormat)
    
    let ultimateLightProduct = UltimateLightVersionFeature()
    ultimateLightProduct.textOperation(text: textToFormat)
    

    わかりやすくするためにこの例をさらに発展させることもできますが、継承ベースに基づいてシステムをサポートする複雑さは今でも明らかです –面倒で柔軟性に欠け
    ます。デコレータは機能を説明する一連のプロトコルであり、機能を拡張するデコレータ クラスの子具象インスタンスへの参照を含む抽象クラスです。
    パターンを使用して上記の例を書き直してみましょう。

    protocol Feature {
    	func textOperation(text: String)
    }
    
    class FeatureDecorator: Feature {
    	private var feature: Feature?
    	
    	init(feature: Feature? = nil) {
    		self.feature = feature
    	}
    	
    	func textOperation(text: String) {
    		feature?.textOperation(text: text)
    	}
    }
    
    class BasicVersionFeature: FeatureDecorator {
    	override func textOperation(text: String) {
    		super.textOperation(text: text)
    		print("\(text.count)")
    	}
    }
    
    class ProfessionalVersionFeature: FeatureDecorator {
    	override func textOperation(text: String) {
    		super.textOperation(text: text)
    		print("\(text.uppercased())")
    	}
    }
    
    class UltimateVersionFeature: FeatureDecorator {
    	override func textOperation(text: String) {
    		super.textOperation(text: text)
    		print("ULTIMATE: \(text)")
    	}
    }
    
    let textToFormat = "Hello Decorator"
    
    let basicProduct = BasicVersionFeature(feature: UltimateVersionFeature())
    basicProduct.textOperation(text: textToFormat)
    
    let professionalProduct = ProfessionalVersionFeature(feature: UltimateVersionFeature())
    professionalProduct.textOperation(text: textToFormat)
    
    let ultimateProduct = BasicVersionFeature(feature: UltimateVersionFeature(feature: ProfessionalVersionFeature()))
    ultimateProduct.textOperation(text: textToFormat)
    
    let ultimateLightProduct = BasicVersionFeature(feature: UltimateVersionFeature())
    ultimateLightProduct.textOperation(text: textToFormat)
    

    これで、あらゆる種類の製品のバリエーションを作成できるようになりました –アプリケーションの起動段階で結合タイプを初期化するだけで十分です。以下の例は、Ultimate + Professional バージョンの作成です。

    ultimateProfessionalProduct.textOperation(text: textToFormat)
    

    ソース

    https://refactoring.guru/ru/design-patterns/decorator

    ソースコード

    https://gitlab.com/demensdeum/patterns

    パターンメディエーター

    メディエーター パターンは、動作デザイン パターンを指します。

    ある日、ジョーク アプリケーションの開発を依頼されました。ユーザーが画面中央のボタンを押すと、アヒルが鳴く面白い音が聞こえます。
    アプリストアにアップロードすると、アプリケーションはヒットします。誰もがあなたのアプリケーションを大騒ぎし、イーロン・マスクは火星の超高速トンネルの次回の打ち上げで自身のインスタグラムで大騒ぎし、ヒラリー・クリントンは討論会でドナルド・トランプを圧倒します。そしてウクライナの選挙に勝利し、成功しましょう!
    アプリケーションの単純な実装は次のようになります。

    class DuckButton {
        func didPress() {
            print("quack!")
        }
    }
    
    let duckButton = DuckButton()
    duckButton.didPress()
    

    次に、犬の鳴き声を追加することにします。そのためには、音を選択するための 2 つのボタンを表示する必要があります –アヒルと犬と一緒に。 DuckButton と DogButton という 2 つのボタン クラスを作成しましょう。
    コードを変更します:

    class DuckButton {
        func didPress() {
            print("quack!")
        }
    }
    
    class DogButton {
        func didPress() {
            print("bark!")
        }
    }
    
    let duckButton = DuckButton()
    duckButton.didPress()
    
    let dogButton = DogButton()
    dogButton.didPress()
    

    さらに成功したら、豚の鳴き声を追加します。ボタンには 3 つのクラスがあります。

    class DuckButton {
        func didPress() {
            print("quack!")
        }
    }
    
    class DogButton {
        func didPress() {
            print("bark!")
        }
    }
    
    class PigButton {
        func didPress() {
            print("oink!")
        }
    }
    
    let duckButton = DuckButton()
    duckButton.didPress()
    
    let dogButton = DogButton()
    dogButton.didPress()
    
    let pigButton = PigButton()
    pigButton.didPress()
    

    ユーザーからは音が重なっているとの苦情が寄せられています。
    これが起こらないようにチェックを追加し、同時にクラスを相互に導入します。

    class DuckButton {
        var isMakingSound = false
        var dogButton: DogButton?
        var pigButton: PigButton?
        func didPress() {
            guard dogButton?.isMakingSound ?? false == false &&
                    pigButton?.isMakingSound ?? false == false else { return }
            isMakingSound = true
            print("quack!")
            isMakingSound = false
        }
    }
    
    class DogButton {
        var isMakingSound = false
        var duckButton: DuckButton?
        var pigButton: PigButton?
        func didPress() {
            guard duckButton?.isMakingSound ?? false == false &&
                    pigButton?.isMakingSound ?? false == false else { return }
            isMakingSound = true
            print("bark!")
            isMakingSound = false
        }
    }
    
    class PigButton {
        var isMakingSound = false
        var duckButton: DuckButton?
        var dogButton: DogButton?
        func didPress() {
            guard duckButton?.isMakingSound ?? false == false && 
                    dogButton?.isMakingSound ?? false == false else { return }
            isMakingSound = true
            print("oink!")
            isMakingSound = false
        }
    }
    
    let duckButton = DuckButton()
    duckButton.didPress()
    
    let dogButton = DogButton()
    dogButton.didPress()
    
    let pigButton = PigButton()
    pigButton.didPress()
    

    あなたの申請が成功したことを受けて、政府はモバイル デバイスでの鳴き声、吠え声、うなり声を平日の午前 9 時から 15 時までのみ許可するという法律を制定することを決定しました。その場合、あなたのアプリケーションのユーザーは、個人用電子機器を使用したわいせつな音の生成により、5 年間の懲役刑に処される危険があります。
    コードを変更します:

    import Foundation
    
    extension Date {
        func mobileDeviceAllowedSoundTime() -> Bool {
            let hour = Calendar.current.component(.hour, from: self)
            let weekend = Calendar.current.isDateInWeekend(self)
            
            let result = hour >= 9 && hour <= 14 && weekend == false
            
            return result
        }
    }
    
    class DuckButton {
        var isMakingSound = false
        var dogButton: DogButton?
        var pigButton: PigButton?
        func didPress() {
            guard dogButton?.isMakingSound ?? false == false &&
                    pigButton?.isMakingSound ?? false == false &&
                     Date().mobileDeviceAllowedSoundTime() == true else { return }
            isMakingSound = true
            print("quack!")
            isMakingSound = false
        }
    }
    
    class DogButton {
        var isMakingSound = false
        var duckButton: DuckButton?
        var pigButton: PigButton?
        func didPress() {
            guard duckButton?.isMakingSound ?? false == false &&
                    pigButton?.isMakingSound ?? false == false &&
                     Date().mobileDeviceAllowedSoundTime() == true else { return }
            isMakingSound = true
            print("bark!")
            isMakingSound = false
        }
    }
    
    class PigButton {
        var isMakingSound = false
        var duckButton: DuckButton?
        var dogButton: DogButton?
        func didPress() {
            guard duckButton?.isMakingSound ?? false == false && 
                    dogButton?.isMakingSound ?? false == false &&
                     Date().mobileDeviceAllowedSoundTime() == true else { return }
            isMakingSound = true
            print("oink!")
            isMakingSound = false
        }
    }
    
    let duckButton = DuckButton()
    let dogButton = DogButton()
    let pigButton = PigButton()
    
    duckButton.dogButton = dogButton
    duckButton.pigButton = pigButton
    
    dogButton.duckButton = duckButton
    dogButton.pigButton = pigButton
    
    pigButton.duckButton = duckButton
    pigButton.dogButton = dogButton
    
    duckButton.didPress()
    dogButton.didPress()
    pigButton.didPress()
    

    突然、懐中電灯アプリケーションが私たちのアプリケーションを市場から追い出し始めます。それに負けないように、「oink-oink」ボタンと残りのボタンを押して懐中電灯を追加しましょう。

    import Foundation
    
    extension Date {
        func mobileDeviceAllowedSoundTime() -> Bool {
            let hour = Calendar.current.component(.hour, from: self)
            let weekend = Calendar.current.isDateInWeekend(self)
            
            let result = hour >= 9 && hour <= 14 && weekend == false
            
            return result
        }
    }
    
    class Flashlight {
    
        var isOn = false
    
        func turn(on: Bool) {
            isOn = on
        }
    }
    
    class DuckButton {
        var isMakingSound = false
        var dogButton: DogButton?
        var pigButton: PigButton?
        var flashlight: Flashlight?
        func didPress() {
            flashlight?.turn(on: true)
            guard dogButton?.isMakingSound ?? false == false &&
                    pigButton?.isMakingSound ?? false == false &&
                     Date().mobileDeviceAllowedSoundTime() == true else { return }
            isMakingSound = true
            print("quack!")
            isMakingSound = false
        }
    }
    
    class DogButton {
        var isMakingSound = false
        var duckButton: DuckButton?
        var pigButton: PigButton?
        var flashlight: Flashlight?
        func didPress() {
            flashlight?.turn(on: true)
            guard duckButton?.isMakingSound ?? false == false &&
                    pigButton?.isMakingSound ?? false == false &&
                     Date().mobileDeviceAllowedSoundTime() == true else { return }
            isMakingSound = true
            print("bark!")
            isMakingSound = false
        }
    }
    
    class PigButton {
        var isMakingSound = false
        var duckButton: DuckButton?
        var dogButton: DogButton?
        var flashlight: Flashlight?
        func didPress() {
            flashlight?.turn(on: true)
            guard duckButton?.isMakingSound ?? false == false && 
                    dogButton?.isMakingSound ?? false == false &&
                     Date().mobileDeviceAllowedSoundTime() == true else { return }
            isMakingSound = true
            print("oink!")
            isMakingSound = false
        }
    }
    
    let flashlight = Flashlight()
    let duckButton = DuckButton()
    let dogButton = DogButton()
    let pigButton = PigButton()
    
    duckButton.dogButton = dogButton
    duckButton.pigButton = pigButton
    duckButton.flashlight = flashlight
    
    dogButton.duckButton = duckButton
    dogButton.pigButton = pigButton
    dogButton.flashlight = flashlight
    
    pigButton.duckButton = duckButton
    pigButton.dogButton = dogButton
    pigButton.flashlight = flashlight
    
    duckButton.didPress()
    dogButton.didPress()
    pigButton.didPress()
    

    その結果、大量のコピー&ペーストコードを含む巨大なアプリケーションができました。内部のクラスはデッドリンクによって相互に接続されています。弱い結合は存在せず、このような奇跡を維持するのは非常に困難です。エラーが発生する可能性が高いため、将来変更される可能性があります。

    メディエーターを使用する

    中間メディエーター クラス ApplicationController を追加しましょう。このクラスはオブジェクトの疎結合を提供し、クラス間の責任を確実に分離し、重複したコードを排除します。
    書き直してみましょう:

    import Foundation
    
    class ApplicationController {
    
        private var isMakingSound = false
        private let flashlight = Flashlight()
        private var soundButtons: [SoundButton] = []
    
        func add(soundButton: SoundButton) {
            soundButtons.append(soundButton)
        }
        
        func didPress(soundButton: SoundButton) {
            flashlight.turn(on: true)
            guard Date().mobileDeviceAllowedSoundTime() && 
                    isMakingSound == false else { return }
            isMakingSound = true
            soundButton.didPress()
            isMakingSound = false
        }
    }
    
    class SoundButton {
        let soundText: String
        
        init(soundText: String) {
            self.soundText = soundText
        }
        
        func didPress() {
            print(soundText)
        }
    }
    
    class Flashlight {
        var isOn = false
    
        func turn(on: Bool) {
            isOn = on
        }
    }
    
    extension Date {
        func mobileDeviceAllowedSoundTime() -> Bool {
            let hour = Calendar.current.component(.hour, from: self)
            let weekend = Calendar.current.isDateInWeekend(self)
            
            let result = hour >= 9 && hour <= 14 && weekend == false
            
            return result
        }
    }
    
    let applicationController = ApplicationController()
    let pigButton = SoundButton(soundText: "oink!")
    let dogButton = SoundButton(soundText: "bark!")
    let duckButton = SoundButton(soundText: "quack!")
    
    applicationController.add(soundButton: pigButton)
    applicationController.add(soundButton: dogButton)
    applicationController.add(soundButton: duckButton)
    
    pigButton.didPress()
    dogButton.didPress()
    duckButton.didPress()
    

    ユーザー インターフェイス アプリケーション アーキテクチャに関する多くの記事で、MVC パターンとその派生について説明しています。モデルはビジネス ロジック データを操作するために使用され、ビューまたは表現はインターフェイスでユーザーに情報を表示し、ユーザーとの対話を提供します。コントローラーはシステム コンポーネントの対話を保証するメディエーターです。

    ソース

    https://refactoring.guru/ru/design-patterns/仲介者

    ソースコード

    https://gitlab.com/demensdeum/patterns/< /p>

    Strategy pattern

    The Strategy pattern allows you to select the type of algorithm that implements a common interface, right while the application is running. This pattern refers to the behavioral design patterns.

    Sun Tzu

    Suppose we are developing a music player with embedded codecs. The built-in codecs imply reading music formats without using external sources of the operating system (codecs), the player should be able to read tracks of different formats and play them. VLC player has such capabilities, it supports various types of video and audio formats, it runs on popular and not very operating systems.

    Imagine what a naive player implementation looks like:

    var player: MusicPlayer?
    
    func play(filePath: String) {
        let extension = filePath.pathExtension
    
        if extension == "mp3" {
            playMp3(filePath)
        }
        else if extension == "ogg" {
            playOgg(filePath)
        }
    }
    
    func playMp3(_ filePath: String) {
        player = MpegPlayer()
        player?.playMp3(filePath)
    }
    
    func playOgg(_ filePath: String) {
        player = VorbisPlayer()
        player?.playMusic(filePath)
    }
    

    Next, we add several formats, which leads to the need to write additional methods. Plus, the player must support plug-in libraries, with new audio formats that will appear later. There is a need to switch the music playback algorithm, the Strategy pattern is used to solve this problem.

    Let’s create a common protocol MusicPlayerCodecAlgorithm, write the implementation of the protocol in two classes MpegMusicPlayerCodecAlgorithm and VorbisMusicPlayerCodecAlgorithm, to play mp3 and ogg files with-but. Create a class MusicPlayer, which will contain a reference for the algorithm that needs to be switched, then by the file extension we implement codec type switching:

    import Foundation
    
    class MusicPlayer {
        var playerCodecAlgorithm: MusicPlayerCodecAlgorithm?
        
    	func play(_ filePath: String) {
                playerCodecAlgorithm?.play(filePath)
    	}
    }
    
    protocol MusicPlayerCodecAlgorithm {
        func play(_ filePath: String)
    }
    
    class MpegMusicPlayerCodecAlgorithm: MusicPlayerCodecAlgorithm {
    	func play(_ filePath: String) {
    		debugPrint("mpeg codec - play")
    	}
    }
    
    class VorbisMusicPlayerCodecAlgorithm: MusicPlayerCodecAlgorithm {
    	func play(_ filePath: String) {
    		debugPrint("vorbis codec - play")	
    	}
    }
    
    func play(fileAtPath path: String) {
    	guard let url = URL(string: path) else { return }
    	let fileExtension = url.pathExtension
    		
    	let musicPlayer = MusicPlayer()
    	var playerCodecAlgorithm: MusicPlayerCodecAlgorithm? 
    		
    	if fileExtension == "mp3" {
                    playerCodecAlgorithm = MpegMusicPlayerCodecAlgorithm()
    	}
    	else if fileExtension == "ogg" {
                    playerCodecAlgorithm = VorbisMusicPlayerCodecAlgorithm()
    	}
    		
    	musicPlayer.playerCodecAlgorithm = playerCodecAlgorithm
    	musicPlayer.playerCodecAlgorithm?.play(path)
    }
    
    play(fileAtPath: "Djentuggah.mp3")
    play(fileAtPath: "Procrastinallica.ogg")
    

    The above example also shows the simplest example of a factory (switching the codec type from the file extension) It is important to note that the Strategy strategy does not create objects, it only describes how to create a common interface for switching the family of algorithms.

    Documentation

    https://refactoring.guru/en/design-patterns/strategy

    Source code

    https://gitlab.com/demensdeum/patterns/

    Iterator pattern

    In this article I will describe the Iterator pattern.
    This pattern refers to the behavioral design patterns.

    Print it

    Suppose we need to print a list of tracks from the album “Procrastinate them all” of the group “Procrastinallica”.
    The naive implementation (Swift) looks like this:

    for i=0; i < tracks.count; i++ {
        print(tracks[i].title)
    }
    

    Suddenly during compilation, it is detected that the class of the tracks object does not give the number of tracks in the count call, and moreover, its elements cannot be accessed by index. Oh…

    Filter it

    Suppose we are writing an article for the magazine “Wacky Hammer”, we need a list of tracks of the group “Djentuggah” in which bpm exceeds 140 beats per minute. An interesting feature of this group is that its records are stored in a huge collection of underground groups, not sorted by albums, or for any other grounds. Let’s imagine that we work with a language without functionality:

    var djentuggahFastTracks = [Track]()
    
    for track in undergroundCollectionTracks {
        if track.band.title == "Djentuggah" && track.info.bpm == 140 {
            djentuggahFastTracks.append(track)
        }
    }
    

    Suddenly, a couple of tracks of the group are found in the collection of digitized tapes, and the editor of the magazine suggests finding tracks in this collection and writing about them. A Data Scientist friend suggests to use the Djentuggah track classification algorithm, so you don’t need to listen to a collection of 200 thousand tapes manually. Try:

    var djentuggahFastTracks = [Track]()
    
    for track in undergroundCollectionTracks {
        if track.band.title == "Djentuggah" && track.info.bpm == 140 {
            djentuggahFastTracks.append(track)
        }
    }
    
    let tracksClassifier = TracksClassifier()
    let bpmClassifier = BPMClassifier()
    
    for track in cassetsTracks {
        if tracksClassifier.classify(track).band.title == "Djentuggah" && bpmClassifier.classify(track).bpm == 140 {
            djentuggahFastTracks.append(track)
        }
    }
    

    Mistakes

    Now, just before sending to print, the editor reports that 140 beats per minute are out of fashion, people are more interested in 160, so the article should be rewritten by adding the necessary tracks.
    Apply changes:

    var djentuggahFastTracks = [Track]()
    
    for track in undergroundCollectionTracks {
        if track.band.title == "Djentuggah" && track.info.bpm == 160 {
            djentuggahFastTracks.append(track)
        }
    }
    
    let tracksClassifier = TracksClassifier()
    let bpmClassifier = BPMClassifier()
    
    for track in cassetsTracks {
        if tracksClassifier.classify(track).band.title == "Djentuggah" && bpmClassifier.classify(track).bpm == 140 {
            djentuggahFastTracks.append(track)
        }
    }
    

    The most attentive ones noticed an error; the bpm parameter was changed only for the first pass through the list. If there were more passes through the collections, then the chance of a mistake would be higher, that is why the DRY principle should be used. The above example can be developed further, for example, by adding the condition that you need to find several groups with different bpm, by the names of vocalists, guitarists, this will increase the chance of error due to duplication of code.

    Behold the Iterator!

    In the literature, an iterator is described as a combination of two protocols / interfaces, the first is an iterator interface consisting of two methods – next(), hasNext(), next() returns an object from the collection, and hasNext() reports that there is an object and the list is not over. However in practice, I observed iterators with one method – next(), when the list ended, null was returned from this object. The second is a collection that should have an interface that provides an iterator – the iterator() method, there are variations with the collection interface that returns an iterator in the initial position and in end – the begin() and end() methods are used in C ++ std.
    Using the iterator in the example above will remove duplicate code, eliminate the chance of mistake due to duplicate filtering conditions. It will also be easier to work with the collection of tracks on a single interface – if you change the internal structure of the collection, the interface will remain old and the external code will not be affected.
    Wow!

    let bandFilter = Filter(key: "band", value: "Djentuggah")
    let bpmFilter = Filter(key: "bpm", value: 140)
    let iterator = tracksCollection.filterableIterator(filters: [bandFilter, bpmFilter])
    
    while let track = iterator.next() {
        print("\(track.band) - \(track.title)")
    }
    

    Changes

    While the iterator is running, the collection may change, thus causing the iterator’s internal counter to be invalid, and generally breaking such a thing as “next object”. Many frameworks contain a check for changing the state of the collection, and in case of changes they return an error / exception. Some implementations allow you to remove objects from the collection while the iterator is running, by providing the remove() method in the iterator.

    Documentation

    https://refactoring.guru/en/design-patterns/iterator

    Source code

    https://gitlab.com/demensdeum/patterns/

    パターン「スナップショット」

    この投稿では、パターン「スナップショット」について説明します。または「メメント」

    このパターンは「行動」を指します。デザインパターン。

    グラフィック エディタを開発していて、ユーザー コマンドに基づいてアクションをロールバックする機能を追加する必要があるとします。このパターンを実装する場合、システム コンポーネントがロールバックされた「アクション」の内部状態にアクセスできないことも非常に重要です。他のシステム コンポーネントはスナップショット オブジェクトにのみアクセスでき、変更することはできません。内部状態を示し、明確でシンプルな外部インターフェイスを提供します。この問題を解決するには、「スナップショット」パターンが使用されます。または「キーパー」。

    作品例「スナップショット」以下に示します:


    クリックするとスプライトが表示され、カールした矢印をクリックするとアクションがキャンセルされます。スプライトが消えます。この例は 3 つのクラスで構成されています。

    <オル>

  • スプライトとグラフィカル インターフェイスが表示されるキャンバス。
  • 画面コントローラー。クリックを処理し、画面のロジックを制御します。
  • 変更のたびに保持されるキャンバスの状態は、必要に応じて、画面コントローラを使用してロールバックされます。
  • パターン「スナップショット」のコンテキストでは、クラスは次のとおりです:

    <オル>

  • キャンバス–ソースでは、このクラスの状態は、後で要求に応じてロールバックできるように「スナップショット」として保存されます。また、ソースは「スナップショット」を転送するときに状態を復元できる必要があります。
  • コントローラー–管理者であるこのクラスは、状態を保存/ロールバックする方法とタイミングを知っています。
  • 状態–スナップショット。ソースの状態に加えて、ロールバック順序を正確に確立できる日付情報またはインデックスを保存するクラスです。
  • このパターンの重要な特徴は、ソースのみがスナップショット内の保存された状態の内部フィールドにアクセスできる必要があることです。これは、スナップショットを外部からの変更 (カプセル化をバイパスして何かを変更したい便利な開発者による) から保護するために必要です。システムロジックを破壊します)。カプセル化を実装するには、組み込みクラスが使用され、C++ ではフレンド クラスを指定する機能が使用されます。個人的には、Rise ではカプセル化を行わないシンプルなバージョンを実装し、Swift で実装する場合は Generic を使用しました。私のバージョンでは – Memento は、同じクラス状態のエンティティにのみ内部状態を与えます。

    ソース

    https://refactoring.guru/design-patterns/memento

    ソースコード

    https://gitlab.com/demensdeum/patterns/< /p>

    訪問者のパターン

    この投稿では、「訪問者」と呼ばれるデザイン パターンについて説明します。または「訪問者」
    このパターンは行動パターンのグループに属します。

    問題を考えてみましょう

    このパターンは主に、早期バインド言語における単一ディスパッチの制限を回避するために使用されます。

    アリス X by NFGPhoto (CC-2.0)
    抽象クラス/プロトコル Band を作成し、MurpleDeep のサブクラスを作成し、2 つのメソッドを持つ Visitor クラスを作成しましょう。 1 つは Band の子孫をコンソールに出力するためのもので、2 つ目は MurpleDeep を出力するためのものです。主なことは、メソッドの名前 (シグネチャ) が同じで、引数がクラスによってのみ異なることです。 Band 引数を指定した中間出力メソッドを使用して Visitor のインスタンスを作成し、MurpleDeep の visit メソッドを呼び出します。
    以下は Kotlin のコードです。

    出力は “これはバンド クラスです

    どうしてそんなことが可能なのでしょうか?!

    なぜこれが起こるのかは、ロシア語を含む多くの記事で賢明な言葉で説明されていますが、コンパイラーがコードをどのように認識するかを想像してみることをお勧めします。おそらくすべてがすぐに明らかになるでしょう。

    問題の解決

    この問題を解決するには多くの解決策があります。次に、訪問者パターンを使用した解決策を検討します。
    Visitor 引数を持つ accept メソッドを抽象クラス/プロトコルに追加し、メソッド内でvisitor.visit(this) を呼び出し、次に accept メソッドのオーバーライド/実装を MurpleDeep クラスに追加します。これにより、決定的かつ冷静に DRY に違反し、再度次のように記述します。 Visitor.visit(this).< br />最終コード:

    ソース

    https://refactoring.guru/ru/デザインパターン/訪問者-ダブルディスパッチ

    ソースコード

    https://gitlab.com/demensdeum/patterns

    フライウェイトパターン

    この投稿では、構造パターン「軽量」について説明します。または「日和見主義者」 (フライ級)
    このパターンは構造パターンのグループに属します。

    以下でパターンがどのように機能するかの例を見てみましょう。


    なぜ必要なのでしょうか? RAM を節約するため。 Java (CPU とメモリを無駄に消費する) が広く使用されている現在では、これはそれほど重要ではなくなりましたが、使用する価値はあるという意見には私も同意します。
    上記の例では40個のオブジェクトしか出力していませんが、120,000個に増やすとその分メモリ消費量も増加します
    。Chromium ブラウザでフライウェイト パターンを使用しないメモリ消費量を見てみましょう。

    パターンを使用しない場合、メモリ消費量は最大 300 メガバイトになります。

    次に、パターンをアプリケーションに追加して、メモリ消費量を確認してみましょう。

    このパターンを使用すると、メモリ消費量は約 200 メガバイトになるため、テスト アプリケーションでは 100 メガバイトのメモリを節約できます。この差はさらに大きくなる可能性があります。

    それはどのように機能しますか?

    上の例では、40 匹の猫を描画します。わかりやすくするために、120,000 匹の猫を描画します。各猫は PNG イメージとしてメモリにロードされ、ほとんどのレンダリングではレンダリング用のビットマップ (実際には bmp) に変換されます。圧縮された PNG はレンダリングに非常に時間がかかるため、これは速度を上げるために行われます。パターンを使用しない場合、12万枚の猫の写真をRAMにロードして描画しますが、パターンを使用すると「軽量」になります。 1 匹の猫をメモリにロードし、さまざまな位置と透明度で 12 万回描画します。重要なのは、猫の画像とは別に座標と透明度を実装することです。レンダリングでは 1 匹の猫だけを受け取り、正しいレンダリングのために座標と透明度を持つオブジェクトを使用します。

    コードではどのようになりますか?

    以下は、言語 Rise< /p>

    パターンを使用しない場合:


    猫の画像はループ内のオブジェクトごとに個別にロードされます。猫の画像。

    使用パターン:

    猫の 1 枚の写真が 12 万個のオブジェクトで使用されています。

    どこで使用されますか?

    Apple の「再利用」などの GUI フレームワークで使用されます。 (再利用) UITableViewCell テーブル セル。これにより、このパターンを知らない初心者にとっては参入障壁が高くなります。ゲーム開発でもよく使用されます。

    ソースコード

    https://gitlab.com/demensdeum/patterns/< /p>

    ソース

    https://refactoring.guru/ru/design-patterns/フライ級
    http://gameprogrammingpatterns.com/flyweight.html

    良い人、悪い人、そして醜いシングルトン

    このメモでは、さまざまな (成功したプロジェクトとそれほど成功していない) プロジェクトに取り組みながら、シングルトン パターン (外国文学におけるシングルトン) を使用したときの私の経験と同僚の経験について説明します。このパターンがどこにも使用できないと私が個人的に考える理由を説明し、チーム内のどのような心理的要因がこのアンチパターンの統合に影響を与えるかについても説明します。チームメンバーの一人が扱いやすく、特別な世話や世話の知識を必要としない小さなかわいい子犬を連れてきたことからすべてが始まり、飼育された野獣で終わった理由を理解しようとしていた倒れた、不自由な開発者全員に捧げます。あなたのプロジェクトを人質に取り、ますます多くの工数を必要とし、ユーザーの神経とお金を食いつぶし、一見単純な実装を評価するためにまったく途方もない数字を生み出すことになります。


    Wolf in sheep’s clothing by SarahRichterArt

    物語は別の宇宙で起こり、すべての偶然はランダムです…

    Cat@Home を使用して自宅で猫を撫でましょう

    誰でも、人生の中で猫を撫でたいという抑えがたい欲求を抱くことがあります。世界中のアナリストは、猫の配達とレンタルのアプリケーションを作成した最初のスタートアップが非常に人気となり、 近い将来モーグリに数兆ドルで買収されるだろうと予測しています。すぐにこれが起こります–チュメニ出身の男がCat@Home アプリケーションを作成し、すぐに億万長者になり、モーグリ 会社は新たな利益源を獲得し、何百万ものストレスを抱えた人々が次の機会を得ることができました。猫に家に来てもらい、さらにアイロンをかけ、落ち着かせてもらいます。

    クローンの攻撃

    ムルマンスク出身の非常に裕福な歯科医、アレクセイ ゴロボロドコは、フォーブス誌の Cat@Home に関する記事に感銘を受け、自分も天文学的なお金持ちになりたいと決意しました。この目標を達成するために、彼は友人を通じて Goldfield – の会社を見つけました。ソフトウェア開発サービスを提供する Wakeboard DevPops は、Cat@Home クローンの開発を同社に発注します。

    優勝チーム

    このプロジェクトは Fur&Pure と呼ばれ、20 人の有能な開発チームに委託されています。次に、5 人からなるモバイル開発チームに注目してみましょう。各チームメンバーはアジャイルとスクラムを武器に自分の仕事に従事し、チームは予定通り (6 か月以内) バグもなく開発を完了し、iStore でアプリケーションをリリースし、100,000 人のユーザーから 5 と評価されます。アプリケーションの素晴らしさ、サービスの素晴らしさ (結局のところ、代替世界) に関するコメント。猫にはアイロンがけが施され、アプリはリリースされ、すべてが順調に進んでいるように見えます。ただし、Cat@Home には猫だけでなく犬もすでに登場しているため、モーグリはスタートアップを数兆ドルで買収することを急いでいません。

    犬が吠え、キャラバンは進む

    アプリケーションの所有者は、アプリケーションに犬を追加する時期が来たと判断し、会社に評価を依頼し、 アプリケーションに犬を追加するために少なくとも 6 か月の猶予を受け取ります。実際には、アプリケーションは再び最初から作成されます。この期間中、Moogle はヘビ、クモ、モルモットをアプリケーションに追加し、Fur&Pur は犬のみを受け取ります。
    なぜこのようなことが起こったのでしょうか?柔軟なアプリケーション アーキテクチャの欠如がすべての原因です。最も一般的な要因の 1 つはシングルトンのアンチパターンです。

    何が問題ですか?

    自宅で猫を注文するには、消費者はリクエストを作成してオフィスに送信する必要があります。オフィスはそれを処理し、猫を連れた宅配便を送ります。宅配便はすでにサービスの代金を受け取っています。
    プログラマーの 1 人がクラス「Cat Application」を作成することにしました。必要なフィールドを指定すると、 このクラスがシングルトンを通じてグローバル アプリケーション空間に組み込まれます。なぜ彼はこんなことをしているのでしょうか?時間を節約するため (30 分の 1 ペニーの節約)。アプリケーションのアーキテクチャを熟考して依存関係の注入を使用するよりも、アプリケーションを公開する方が簡単だからです。次に、他の開発者がこのグローバル オブジェクトを選択し、クラスをそれにバインドします。 たとえば、すべての画面自体がグローバル オブジェクト「Cat Request」にアクセスします。アプリケーション上のデータを表示します。その結果、このようなモノリシックなアプリケーションがテストされ、リリースされる
    ことになります。すべてがうまくいっているように見えますが、突然、アプリケーションに犬のリクエストを追加するという要件を要求する顧客が表示されます。チームは、システム内のいくつのコンポーネントがこの変更によって影響を受けるかを必死で評価し始めます。分析の最後に、アプリケーションに「猫のリクエスト」だけでなくリクエストも受け入れるように教えるには、コードの 60 ~ 90% をやり直す必要があることが判明しました。しかし、「犬の申請」も同様です。少なくとも 2 頭の動物に対処するには、この段階で他の動物の追加を評価することはすでに無意味です。

    シングルトンを防ぐ方法

    まず、要件収集の段階で、柔軟で拡張可能なアーキテクチャを作成する必要性を明示します。第二に、弱点の調査を義務付けて、製品コードの独立したレビューを並行して実施する価値があります。あなたが開発者で、シングルトンが大好きな場合は、手遅れになる前に正気に戻ることをお勧めします。そうしないと、眠れない夜と神経のすり減りが確実です。多数のシングルトンを含むレガシー プロジェクトに取り組んでいる場合は、できるだけ早くシングルトンまたはプロジェクトを削除するようにしてください。
    シングルトン – グローバル オブジェクト/ 変数のアンチパターンから依存関係の注入に切り替える必要があります。必要なすべてのデータが初期化段階でクラスのインスタンスに与えられる最も単純な設計パターンであり、グローバル空間にさらにバインドする必要はありません。

    ソース

    https://stackoverflow. com/questions/137975/what-is-so-bad-about-singletons
    http://misko.hevery.com/2008/08/17/singletons-are-pathological-liars/
    https://blog.ndepend.com/singleton-pattern-costs/

    デスマスク開発レポート1

    新しい非常設セクション「開発者日記」または外国風の開発日記
    など。ゲーム デスマスク の開発は本格化しています。エンジンのロゴ2019 年の Flame Steel Engine ゲーム用に追加されました。島ごとに初期マップを選択する画面 (緑、赤、黒、白)、迷宮の壁、天井、床のテクスチャの出力、プレイエリアのサイズの拡大です。 p>


    レッド ゾーンの都市の地図

    次に、Doom スタイルのスプライトの代わりに環境用の 3D モデルを追加する予定です。また、武器、ボックス、敵、友人のモデルも追加する予定です。ゲームプレイでは、通貨、ショップ、戦利品のある興味深い場所を示すゲームマップの一部を購入する機能、および「デスマスク」の可能な場所を追加することが計画されています。電脳迷宮を旅する仲間を雇える機能も追加したいと思っています。
    ニュースをフォローしてください。

    スイフト 4.2.3 – Ubuntu 18.10

    Ubuntu 18.10 で実行するために必要なライブラリを使用して Swift を構築します。 Apple Web サイトで入手可能な最新バージョン – Ubuntu 18.04の場合。公式 Web サイトのアセンブリに基づいており、Ubuntu 18.04 のライブラリが追加されています。また、bash ターミナルの PATH と LD_LIBRARY_PATH を追加するサンプル スクリプトも追加しました:
    http://www.mediafire.com/file/lrs74mvoj3fti03/swift4.2.3.ubuntu.18.10.x86_64.tar.gz/file

    宣言型言語Zakaz

    私は純粋な宣言型プログラミング言語を皆さんに紹介します –ザカズ。新しい言語の主なアイデア –アプリケーションには、自由形式で記述された実行コマンドが含まれており、「実行者」によって実行される必要があります。 「出演者」がいない場合は、コマンドを実行できない場合、プログラムの実行は停止します。アプリケーションは技術仕様 (tez) と呼ばれ、.tez 拡張子が必要です。 Zakaz 構文には 2 つのルールが必要です。

    • 各コマンドは新しい行で始まります
    • 各コマンドは人間が理解できる形式的な言語で記述する必要があります

    Hello World.tez の例:

    画面上に 「Hello World」 テキストを表示します「Zakaz 'tez' example」 テキストを画面に表示します

    動作原理の説明を表示し、Firefox ブラウザでサイト http://demensdeum.com を開く仕様の例

    画面に「ウェブサイトのデモを表示」 テキストを表示しますShow 「この 'tez' を実行するには、システムに Firefox がインストールされている必要があります。\"システム\" C 関数" テキストを表示「\"FirefoxPerformer\" が Zakaz ランタイムに割り当てられています。マニュアルを確認してください詳細については、画面上のテキストFirefox でアドレス "http://demensdeum.com" の Web サイトを表示します

    上記の例は “executor” と一緒に実行する必要があります。 FirefoxPerformer は、最新のコマンドを処理して Firefox を通じてサイトをレンダリングできます

    ./ZakazRuntime openDemensdeumSite.tez FirefoxPerformer

    エグゼキュータを実装するには、抽象クラス ZakazRuntime::Performer を使用してエグゼキュータを動的ライブラリとして実装し、createPerformer() グローバル関数メソッドからスマート ポインタとともに返す必要があります。 FirefoxPerformer 実装を例として使用できます。

    ソース コード

    https://gitlab.com/demensdeum/zakiz