刹那的思考をメモる

AIのミトコンドリアになれるといいね

gofのデザインパターン Visitor

背景

llvm.org これを読んでいて名前が出てきた.(チュートリアルを進めるうえではいらないが..)Gofも流し見を数年前にしたきり,詳細を知らないので良い機会なので学ぶ

gof(ギャングオブフォー)

デザインパターン23種. ja.wikipedia.org

Visitor

Visitor パターンは、オブジェクト指向プログラミング およびソフトウェア工学 において、 アルゴリズムをオブジェクトの構造から分離するためのデザインパターンである。分離による実用的な結果として、既存のオブジェクトに対する新たな操作を構造を変更せずに追加することができる。 by wikipedia

使い所

Visitorの使い所として, 同じ関数名で複数の型ごとに処理を変えたいときに使うという想定で問題ないですか

========以下GPT==============

はい、その想定で問題ありません。Visitor パターンは、特に同じ関数名(インターフェースのメソッド)で異なる型に対して異なる処理を実行したい場合に非常に有効です。このパターンは、異なる型のオブジェクトがそれぞれ独自の処理を要求する状況での多態性と拡張性を提供します。

Visitor パターンの役割

  1. 型ごとの処理の分離: Visitor パターンを使用すると、各オブジェクトの型ごとに異なる処理をvisitメソッドに実装できます。これにより、処理をオブジェクトの型に基づいて動的に変更することができます。

  2. オープン/クローズド プリンシプルのサポート: ソフトウェアエンティティは拡張に対してオープンであり、修正に対してクローズドであるべきという設計原則をサポートします。新しい型のオブジェクトが追加された場合、既存のクラスを変更することなく、新しい Visitor を実装することで対応できます。

  3. 単一責任の原則: 各visitメソッドは特定の型のオブジェクトに対する処理のみを担当します。これにより、クラスとメソッドの責任が明確に区分され、コードの保守性が向上します。

具体的な使用例

  • コンパイラ設計: 抽象構文木(AST)のノードごとに異なるコード生成処理を実行する際にVisitorパターンを使用します。
  • ドキュメント処理: 異なるタイプのドキュメント要素(テキスト、画像、表など)に対して、異なるレンダリング処理や書式設定を適用する場合に役立ちます。
  • システム監視とレポート: 異なる種類のシステムリソース(CPU、メモリ、ディスクなど)に対して、特定の監視またはログ処理を行う際に使用します。

まとめ

Visitor パターンは、特に複数の異なるオブジェクト型に対して同一のインターフェースを通じて異なる振る舞いを実装したい場合に非常に有効なデザインパターンです。型に基づいた処理の柔軟性と拡張性を提供するため、多くのアプリケーションでその利点が活かされています。

========以上GPT==============

実装例(Pretty Printer(pprint)とは)

Pretty Printerとは

Pretty Printerは、コードやデータ構造を整形して、人間が読みやすい形式で表示するツールです。例えば、プログラムのソースコードXML文書、抽象構文木(AST)などの構造を美しく印刷(表示)するために使われます。

json(簡易版)をきれいに表示

真面目にやるならrfcRFC 8259)を準拠する必要(正確には読んだことがないので完全に準拠しているか不明)があるが,今回はBool , Number, String,Arrayを許し再帰的な構造を許す jsonを表示するpprintを実装.parserの実装もしない.Rustで実装します.

抽象構文木の定義

enum Json {
    Null,
    Bool(bool),
    Number(f64),
    String(String),
    // Array(Vec<Json>), 後で追加
    Object(Vec<(String, Json)>),
}

コード

#[allow(dead_code)]
enum Json {
    Null,
    Bool(bool),
    Number(f64),
    String(String),
    Array(Vec<Json>),
    Object(Vec<(String, Json)>),
}

trait Vistor {
    fn visit_null(&mut self)->String;
    fn visit_bool(&mut self, v:&bool)->String;
    fn visit_number(&mut self, v:&f64)->String;
    fn visit_string(&mut self, v:&String)->String;
    fn visit_array(&mut self, v:&mut Vec<Json>)->String;
    fn visit_object(&mut self, v:&mut Vec<(String, Json)>)->String;
}

impl Json {
    fn accept(&mut self, vistor: &mut dyn Vistor) -> String {
        match self {
            Json::Null => vistor.visit_null(),
            Json::Bool(b) => vistor.visit_bool(b),
            Json::Number(n) => vistor.visit_number(n),
            Json::String(s) => vistor.visit_string(s),
            Json::Array(a) => vistor.visit_array(a),
            Json::Object(o) => vistor.visit_object(o),
        }
    }
}

struct JsonPrinter {
    indent: usize,
}

impl Vistor for JsonPrinter {
    fn visit_null(&mut self) -> String {
        "null".to_string()
    }

    fn visit_bool(&mut self, v: &bool) -> String {
        v.to_string()
    }

    fn visit_number(&mut self, v: &f64) -> String {
        v.to_string()
    }

    fn visit_string(&mut self, v: &String) -> String {
        format!("\"{}\"", v)
    }

    fn visit_array(&mut self, v: &mut Vec<Json>) -> String {
        let mut result = "[".to_string();
        for (i, item) in v.iter_mut().enumerate() {
            if i > 0 {
                result.push_str(", ");
            }
            result.push_str(item.accept(self).as_str());
        }
        result.push_str("]");
        result
    }

    fn visit_object(&mut self, v: &mut Vec<(String, Json)>) -> String {
        let mut result = "".to_string();
        result.push_str("{\n");
        
        self.indent += 1;
        for (i, (key, value)) in v.iter_mut().enumerate() {
            if i > 0 {
                result.push_str(", \n");
            }
            result.push_str(&"  ".repeat(self.indent));
            result.push_str(&format!("\"{}\": {}", key, value.accept(self)));
        }
        
        self.indent -= 1;
        result.push_str("\n");
        result.push_str(&"  ".repeat(self.indent));
        result.push_str("}");
        result
    }
    
}

fn main() {
    let mut json = Json::Object(vec![
        ("name".to_string(), Json::String("John".to_string())),
        ("age".to_string(), Json::Number(30.5)),
        ("is_student".to_string(), Json::Bool(false)),
        ("courses".to_string(), Json::Object(vec![
            ("name".to_string(),Json::String("Math".to_string())),
            ("day".to_string(),Json::String("Monday".to_string())),
        ])),
        ("grades".to_string(), Json::Array(vec![
            Json::Number(90.5),
            Json::Number(85.0),
            Json::Number(88.5),
        ])),
    ]);

    let mut printer = JsonPrinter{indent: 0};
    println!("{}", json.accept(&mut printer));
}

結果

{
  "name": "John", 
  "age": 30.5, 
  "is_student": false, 
  "courses": {
    "name": "Math", 
    "day": "Monday"
  }, 
  "grades": [90.5, 85, 88.5]
}

まとめ

実装すると,大体理解できた.確かにコンパイラ使うときには使えそう.コンパイラにdynを付けよと言われてつけたけど, よくわかっていない.認識的には「traitは実装によってメモリサイズが可変なのでそれを伝えるため」今度読むようにキーワードをメモ 仮想メソッドテーブル(vtable),動的ディスパッチ,動的サイズ型(Dynamically Sized Types、DST)

qnighy.hatenablog.com