Gang of Four パターンのリスト –面接で不合格になる可能性があるのと同じパターンです。
生成パターン
構造パターン
行動パターン
Soft & Games
インタープリター パターンは、動作デザイン パターンを指します。このパターンを使用すると、AST ツリーを操作して独自のプログラミング言語を実装できます。AST ツリーの頂点は、言語の機能を提供する Interpret メソッドを実装する終端式と非終端式です。
違いは何ですか?違いは、解釈は終端式で終了しますが、非終端式の場合は、入力されるすべての頂点/引数にわたって深く継続されることです。 AST ツリーが非終端式のみで構成されている場合、アプリケーションは決して完了しません。どのプロセスにも特定の有限性が必要です。この有限性が終端式の正体であり、通常、終端式には文字列などのデータが含まれます。
AST ツリーの例を以下に示します。

Dcoetzee、CC0、ウィキメディア コモンズ経由
ご覧のとおり、終端式は定数と変数であり、残りは非終端式です。
インタープリタの実装には、AST ツリーに入力された言語文字列の解析は含まれません。終端式と非終端式のクラスを実装し、入力で Context 引数を使用してメソッドを解釈し、式の AST ツリーを作成し、ルート式で Interpret メソッドを実行するだけで十分です。コンテキストを使用して、実行時にアプリケーションの状態を保存できます。
パターンには以下が含まれます:
C# でのクライアントの例
static void Main(string[] args)
{
var context = new Context();
var initialProgram = new PerformExpression(
new IExpression[] {
new SetExpression("alpha", "1"),
new GetExpression("alpha"),
new PrintExpression(
new IExpression[] {
new ConstantExpression("Hello Interpreter Pattern")
}
)
}
);
System.Console.WriteLine(initialProgram.interpret(context));
}
}
C# での抽象式の例
{
String interpret(Context context);
}
C# のターミナル式の例 (文字列定数)
{
private String constant;
public ConstantExpression(String constant) {
this.constant = constant;
}
override public String interpret(Context context) {
return constant;
}
}
C# の非終端式の例 (区切り文字「;」を使用して、下位頂点の結果を開始して連結する)
{
public PerformExpression(IExpression[] leafs) : base(leafs) {
this.leafs = leafs;
}
override public String interpret(Context context) {
var output = "";
foreach (var leaf in leafs) {
output += leaf.interpret(context) + ";";
}
return output;
}
}
知られているように、すべてのチューリング完全言語は同等です。オブジェクト指向パターンを関数型プログラミング言語に移植することは可能ですか?
実験として、Elm という Web 用の FP 言語を取り上げてみましょう。 Elm にはクラスはありませんが、レコードとタイプがあるため、次のレコードとタイプが実装に関係します。
Elm で使用可能な式のセット全体の解釈を実装する関数の例:
case input.expression of
Constant text ->
{
output = text,
context = input.context
}
Perform leafs ->
let inputs = List.map (\leaf -> { expressionLeaf = leaf, context = input.context } ) leafs in
let startLeaf = { expressionLeaf = (Node (Constant "")), context = { variables = Dict.empty } } in
let outputExpressionInput = List.foldl mergeContextsAndRunLeafs startLeaf inputs in
{
output = (runExpressionLeaf outputExpressionInput).output,
context = input.context
}
Print printExpression ->
run
{
expression = printExpression,
context = input.context
}
Set key value ->
let variables = Dict.insert key value input.context.variables in
{
output = "OK",
context = { variables = variables }
}
Get key ->
{
output = Maybe.withDefault ("No value for key: " ++ key) (Dict.get key input.context.variables),
context = input.context
}
ソース コードを AST ツリーに解析することはインタープリター パターンには含まれていません。ソース コードを解析するにはいくつかの方法がありますが、それについてはまた別の機会に説明します。
Elm のインタープリタの実装では、頂点の解析と下位頂点の解析という 2 つの関数で構成される単純なパーサーを AST ツリーに作成しました。
parseLeafs state =
let tokensQueue = state.tokensQueue in
let popped = pop state.tokensQueue in
let tokensQueueTail = tail state.tokensQueue in
if popped == "Nothing" then
state
else if popped == "Perform(" then
{
tokensQueue = tokensQueue,
result = (state.result ++ [Node (parse tokensQueue)])
}
else if popped == ")" then
parseLeafs {
tokensQueue = tokensQueueTail,
result = state.result
}
else if popped == "Set" then
let key = pop tokensQueueTail in
let value = pop (tail tokensQueueTail) in
parseLeafs {
tokensQueue = tail (tail tokensQueueTail),
result = (state.result ++ [Node (Set key value)])
}
else if popped == "Get" then
let key = pop tokensQueueTail in
parseLeafs {
tokensQueue = tail tokensQueueTail,
result = (state.result ++ [Node (Get key)])
}
else
parseLeafs {
tokensQueue = tokensQueueTail,
result = (state.result ++ [Node (Constant popped)])
}
parse tokensQueue =
let popped = pop tokensQueue in
let tokensQueueTail = tail tokensQueue in
if popped == "Perform(" then
Perform (
parseLeafs {
tokensQueue = tokensQueueTail,
result = []
}
).result
else if popped == "Set" then
let key = pop tokensQueueTail in
let value = pop (tail tokensQueueTail) in
Set key value
else if popped == "Print" then
Print (parse tokensQueueTail)
else
Constant popped
https://gitlab.com/demensdeum /patterns/-/tree/master/interpreter/elm
https://gitlab.com/demensdeum/patterns/-/tree/master/interpreter/csharp
https://en.wikipedia.org/wiki/Interpreter_pattern
https://elm-lang.org/
https://docs.microsoft.com/en-us/dotnet/csharp/
このノートでは、アプリケーションの開発、サポート、およびチーム開発環境におけるアーキテクチャ上の決定の重要性について書きます。
セルフ手術用ナプキンのルシファー・ゴルゴンゾーラ教授。ルーブ・ゴールドバーグ
若い頃、私はタクシー注文アプリケーションの開発に取り組んでいました。プログラムでは、乗車場所と降車場所を選択し、旅行費用と料金の種類を計算し、実際にタクシーを注文することができます。リリース前の最終段階でアプリケーションを受け取り、いくつかの修正を加えた後、アプリケーションは AppStore でリリースされました。すでにその段階で、チーム全体は、実装が非常に不十分で、デザインパターンが使用されておらず、システムのすべてのコンポーネントが緊密に接続されており、一般に、それを1つの大きな連続クラス(神オブジェクト)に書き込むことが可能であることを理解していました。何も変わらなかっただろうから、階級が責任の境界をどのように混同し、その総量においてデッドカップリングで互いに重なり合ったかということだ。その後、経営陣は正しいアーキテクチャを使用してアプリケーションを最初から作成することを決定し、それが実行され、最終製品は数十の B2B クライアントに実装されました。
しかし、私は過去の建築に関する奇妙な出来事について説明します。その出来事から、私は時々夜中に冷や汗をかきながら目が覚めたり、昼間に突然思い出してヒステリックに笑い始めたりすることがあります。問題は、最初にポールにいる男を攻撃できなかったということで、これによりアプリケーションの大部分がダウンしましたが、まず最初に。
その日は通常の営業日で、顧客の 1 人がアプリケーションのデザインを少し改良するというタスクを受けました –受け取り住所の選択画面の中央にあるアイコンを数ピクセル上に移動するのは簡単です。さて、専門的にタスクを 10 分で見積もったので、何も疑うことなくアイコンを 20 ピクセル上に上げ、タクシーの注文を確認することにしました。
何?アプリに注文ボタンが表示されなくなりました?どうしてこんなことになったのでしょうか?
自分の目を信じられませんでした。アイコンを 20 ピクセル上げた後、アプリケーションに注文を続けるボタンが表示されなくなりました。変更をロールバックすると、ボタンが再び表示されました。ここで何かが間違っていました。デバッガで 20 分を費やした後、重複するクラスへの呼び出しのスパゲッティを解くのに少しうんざりしましたが、*画像を移動するとアプリケーションのロジックが実際に変わる*ことがわかりました。
すべては中央のアイコンに関するものでした –ポールの上にいる男性が、カードを動かすときに飛び上がってカメラの動きをアニメーション化しました。このアニメーションの後に、下部のボタンが消えました。どうやらプログラムは、20 ピクセル移動した男性がジャンプ中であると判断したため、内部ロジックに従って確認ボタンを非表示にしました。
どうしてこのようなことが起こるのでしょうか?画面の *状態* は本当にステート マシンのパターンではなく、ポール上の男の位置の *表現* に依存するのでしょうか?
その結果、マップが描画されるたびにアプリケーション *画面の中央を視覚的に突いて*、そこに何があるかを確認します。ポールの上に人がいる場合は、マップ シフト アニメーションが終了したことを意味し、表示する必要があります。ボタン。男性がそこにいない場合は、マップが移動し、ボタンが非表示になる必要があります。
上記の例では、すべて問題ありません。第一に、これはゴールドバーグ マシン (難解なマシン) の例であり、第二に、開発者がチーム内の他の開発者と何らかの形で対話することに消極的であることの例です (何も考えずに理解してみてください)私)、第三に、SOLID、パターン(コード臭)、MVC 違反などに基づいてすべての問題をリストできます。
このようなことをしないようにし、あらゆる方向に発展し、同僚の仕事を助けてください。皆さん明けましておめでとうございます)
https://ru.wikipedia.org/wiki/Goldberg_Machine
https://ru.wikipedia.org/wiki/SOLID
https://refactoring.guru/ru/refactoring/smells
https://ru.wikipedia.org/wiki/Model -View-Controller
https://refactoring.guru/ru/design-patterns/state

ファサードとは、構造設計パターンを指します。複雑なシステムの操作を可能にする単一のインターフェイスを提供し、クライアントがこれらのシステムに関する実装の詳細を持たないようにできるため、コードが簡素化され、クライアントと下位レベルのシステム間の疎結合が実装されます。 GoF にはファサードの良い例があります。さまざまな目標を追求するさまざまなクライアントに、単一のコンパイラ ファサード インターフェイスを通じてコードをアセンブルする機能を提供するプログラミング言語コンパイラ。
https://refactoring.guru/ru/design-patterns/facade
https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612

抽象ファクトリー–特定のクラスを指定せずに、関連オブジェクトを作成するためのインターフェイスを提供します。
このパターンの別名がとても気に入っています – キット (キット)
これはファクトリ メソッドに非常に似ていますが、抽象ファクトリは作成されるオブジェクト間の関係を記述する必要があります。そうでない場合は、単なる神オブジェクトになります。 すべてを生み出すアンチパターンは行き当たりばったりです。
メガネ用の AR フレームワークを開発するところを想像してください。屋内ナビゲーションの矢印、店舗のアイコン、興味深い場所、ウィンドウ、ユーザーが現在いる場所に関する情報を含むボタンを画面上に表示します。
同時に、AR 環境コントロールの外観と動作をカスタマイズする機能も必要です。この場合にこそ、Set パターンを使用する必要があります。
Abstract Factory と Abstract Products のインターフェースを書いてみましょう –親プロトコル、AR 環境要素:
protocol ARFactory {
func arrow() -> ARArrow
func icon() -> ARIcon
func button() -> ARButton
func window() -> ARWindow
}
protocol ARArrow {
var image: { get }
func handleSelection()
}
protocol ARIcon {
var image: { get }
var title: String
}
protocol ARButton {
var title: String
func handleSelection()
}
protocol ARWindow {
var title: String
var draw(canvas: Canvas)
}
キット開発者は、Abstract Factory インターフェイスに基づいて Concrete Factory を実装する必要があります。また、アプリケーションの残りの部分は、コードを変更せずにファクトリで動作できるように、すべての要素をまとめて実装する必要があります。< /p>
https://refactoring.guru/ru/design-patterns /abstract-factory
https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612
ファクトリー メソッド パターンは、ジェネレーティブ デザイン パターンを指します。
このパターンは、特定のクラスのオブジェクトを作成するためのインターフェイスの作成を記述します。簡単そうに思えますよね?
AR メガネを操作するためのフレームワークを開発しているとします。頭を横に傾けると、利用可能なアプリケーションのメニューがユーザーの目の前に表示されるはずです。アプリケーションは、当社のフレームワークのクライアントであるサードパーティ企業によって開発されます。当然のことながら、どのアプリケーション、アイコン、名前が表示されるべきかわからないため、アイコンとアプリケーションに関する関連情報を実装するためのインターフェイスを提供する必要があります。これを製品:
と呼びましょう。
protocol Product {
var name: String { get }
var image: Image { get }
var executablePath: String { get }
}
次に、クライアントが特定の製品用の一連のアプリケーションの発行を実装できるように、インターフェイスを提供する必要があります。名前が付いたアプリケーション アイコンの配列。これはフレームワーク内ですでに描画されています。
このインターフェースを書いてみましょう – プロダクトの配列を返すファクトリ メソッドを含むクリエイターインターフェース。
protocol Creator {
func factoryMethod() -> [Product]
}
AR フレームワークの最初のクライアントは 7B – 社でした。 ホンジュラスのコーヒーメーカー用ソフトウェアの大手サプライヤー。彼らは、屋内マップ モードを使用して、コーヒーを淹れたり、水や豆が入っているかどうかを確認したり、最寄りのコーヒー メーカーへの道を示す機能を備えた拡張現実グラスを販売したいと考えています。
彼らはソフトウェアの開発を引き受けます。私たちは、アプリケーションのリストとその詳細を正しく表示するために、クリエイター インターフェイスと 製品 インターフェイスに関するドキュメントを提供することのみを求められます。
ドキュメントを転送した後、7B 社は Creator インターフェースを使用して Specific Creator を実装します。アプリケーションアイコンの配列を返すクラス。アイコン アプリケーション自体は、製品 インターフェースを実装する特定の製品 クラスです。
特定の製品のコード例:
class CoffeeMachineLocator: implements Product {
let name = “7B Coffee Machine Locator v.3000”
let image = Image.atPath(“images/locator.tga”)
let executablePath = “CoffeeMachineLocator.wasm”
}
class iPuchinno: implements Product {
let name = “iPuchinno 1.0.3”
let image = Image.atPath(“images/puchino.pvrtc”)
let executablePath = “neutron/ipuchBugFixFinalNoFreezeFixAlpha4.js”
}
クラス Concrete Creator は、2 つのアプリケーションの配列を提供します。
class 7BAppsCreator: implements Creator {
func factoryMethod() -> [Product] {
return [CoffeeMachineLocator(), iPuchinno()]
}
}
この後、7B 社はコンクリート製品、コンクリート クリエーターのライブラリを編集し、それを当社のフレームワークと組み合わせて、コーヒー メーカー用の AR グラスの販売を開始します。 私たちの側での追加は必要ありません。
https://refactoring.guru/ru/design-patterns/command
https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612
コマンド パターンは動作設計パターンを指します。
これは私が長い間こだわってきたパターンです。単純すぎて非常に複雑です。しかし、個人的には、独学の素晴らしさは、特定のトピックをあらゆる角度から研究するための時間をいつでも手に入れられることだと思います。
したがって、GoF では、適用可能性が非常に簡潔かつ明確に説明されています。
リクエストをオブジェクトとしてカプセル化し、さまざまなリクエストでクライアントをパラメータ化し、キューを使用し、リクエストをログに記録し、キャンセル操作を実行できるようにします。
次に、説明にあるコマンドの単純なバージョンを実装してみましょう。
string fakeTrumpsRequest = “SELECT * from Users where name beginsWith DonaldTrump”
リクエストは文字列クラス オブジェクトにカプセル化されており、クライアントの設定、キューへのコマンドの追加、ログ記録、キャンセル(「スナップショット」パターンを使用)に使用できます。
SQL クエリなどを実行するにはこれで十分だと思いますが、実装の詳細、さまざまなアプリケーション オプション、パターンのコード ベース、クライアント ロール、補助クラスも大きく異なります。
コマンド パターンは、単一の execute() メソッドを含む コマンド プロトコル で始まります。次に特定のコマンドとレシーバーが続きます。CC はレシーバー上で操作を実装し、レシーバーとアクションの間の接続を記述します。何か不明な点はありますか?私もそうですが、先に進みましょう。 クライアントは特定のコマンドのインスタンスを作成し、 それをレシーバーに関連付けます。 依頼者 – コマンドを起動するプロセスを実行するオブジェクト。
例を使用してそれを理解してみましょう。myPhone で myOS を更新したいとします。これを行うには、myOS_Update アプリケーションを起動します。その中で [Update Now] ボタンを押すと、10 秒後にシステムが更新されます。アップデートが成功したことを報告します。
上記の例のクライアント は myOS_Update! アプリケーション、呼び出し元 は「今すぐ更新!」ボタンで、特定のコマンド を起動します。 b>レシーバーにアクセスするexecute() メソッドを使用してシステムを更新します–オペレーティング システムのアップデート デーモン。
myOS_Update アプリケーションの UI を受け入れましょう。非常に優れていたため、他のオペレーティング システムを更新するためのインターフェイスを提供するために、別の製品として販売することにしました。この場合、ライブラリを介して拡張機能をサポートするアプリケーションを実装します。ライブラリには特定のコマンド、 レシーバーの実装があり、静的/不変の呼び出し元は残します。 、クライアント、プロトコルコマンド。
したがって、コードは変更されないままであるため、変更可能なコードをサポートする必要はありません。特定のコマンド のコード内のエラーにより、クライアント側で実装された場合にのみ問題が発生する可能性があります。 受信機。また、この実装では、メイン アプリケーションのソース コードを転送する必要はありません。つまり、コマンド パターンを使用してコマンドと UI インタラクションをカプセル化しました。
https://refactoring.guru/ru/design-patterns/command
https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612
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
複合パターンは構造設計パターンを指し、国内情報源では「コンポジター」として知られています。
アプリケーションを開発しているとします。写真アルバム。ユーザーはフォルダーを作成し、そこに写真を追加したり、その他の操作を実行したりできます。フォルダー内のファイルの数、すべてのファイルとフォルダーの合計数を表示する機能は必ず必要です。
ツリーを使用する必要があることは明らかですが、シンプルで便利なインターフェイスを備えたツリー アーキテクチャを実装するにはどうすればよいでしょうか?複合パターンが役に立ちます。
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 -委任付き

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/プロキシ
プロトタイプ パターンは、ジェネレーティブ デザイン パターンのグループに属します。
私たちが出会い系アプリ 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 パターンを使用するのは望ましくないことに注意してください。これにより通常、コードの可読性が不必要に複雑になり、関連するサポートの問題が発生します。すべては適度に行う必要があります。

民間航空機のメディア システム用のビデオ プレーヤー画面を開発しているとします。プレーヤーは、ビデオ ストリームをロードして再生し、ユーザーがダウンロード プロセスの停止、巻き戻し、その他の通常の操作を実行できる必要があります。選手
です。プレーヤーがビデオ ストリームの次のチャンクをキャッシュし、再生に十分なチャンクがあることを確認し、ユーザーに対してフラグメントの再生を開始し、同時に次のチャンクのダウンロードを継続したとします。
この時点で、ユーザーはビデオの途中まで巻き戻します。つまり、現在のフラグメントの再生を停止し、新しい位置から読み込みを開始する必要があります。ただし、これを実行できない状況もあります。ユーザーは、航空安全に関するビデオが表示されている間、ビデオ ストリームの再生を制御できません。この状況を確認するために 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
パターン手法とは、動作設計パターンを指します。このパターンは、クラスのロジックの一部をオンデマンドで置き換え、全体の部分は子孫に対して変更しないままにする方法を説明します。

クライアント銀行を開発していると仮定して、承認モジュールを開発するタスクを考えてみましょう –ユーザーは抽象ログイン データを使用してアプリケーションにログインできる必要があります。
認可モジュールはクロスプラットフォームである必要があり、さまざまな認可テクノロジをサポートし、さまざまなプラットフォームの暗号化されたデータを保存する必要があります。モジュールを実装するには、クロスプラットフォーム 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/ a>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>
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.

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.
https://refactoring.guru/en/design-patterns/strategy
https://gitlab.com/demensdeum/patterns/
In this article I will describe the Iterator pattern.
This pattern refers to the behavioral design patterns.
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…
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) } }
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.
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)") }
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.
https://refactoring.guru/en/design-patterns/iterator
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 に関する記事に感銘を受け、自分も天文学的なお金持ちになりたいと決意しました。この目標を達成するために、彼は友人を通じて 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/