Upgrade to Pro — share decks privately, control downloads, hide ads and more …

軽率にインターフェース使ってる? / Are you using the interface carelessly?

軽率にインターフェース使ってる? / Are you using the interface carelessly?

にー兄さん

August 06, 2022
Tweet

More Decks by にー兄さん

Other Decks in Programming

Transcript

  1. 軽率に
    インターフェース使ってる?
    にー兄さん(@ninisan_drumath)

    View Slide

  2. アジェンダ
    1. インターフェースのおさらい
    2. メソッドやプロパティの強制実装
    3. 情報の隠蔽
    4. 抽象への依存
    5. おわりに

    View Slide

  3. 諸注意(という名の自己防衛)
    インターフェースの説明にはC#言語を用います
    基本C#8までの文法で説明します(一部8以上を含みますスミマセン)
    個人的な考えが含まれます
    一般論として受け止めすぎないでください
    「こうするべき」という主張をする意図はありません
    参考としてご覧ください
    マサカリの持ち込みはご遠慮ください

    View Slide

  4. 発表者について
    にー兄さん
    23歳・学生
    基本武器はUnity C#とTypeScript(Node.js)
    他C++, Zig, Rust, Java, Python, Ruby, VisualBasic, CoffeeScriptなど
    Unity 歴:趣味8年 / 実務2年半
    C#歴  :6,7年(Unity JSで書いてる時期がある)
    中長期インターンあわせて5社でUnityの実務コードを書いたことがあったり

    View Slide

  5. 今回のターゲット
    インターフェースを知っているが軽率に使えていない人
    (インターフェースをそもそも知らない人)
    設計ができるようになってステップアップしたいひと
    オブジェクト指向プログラミングの理解を深めたいひと
    プログラミングに自信がない人

    View Slide

  6. 内容と目的
    インターフェースの使いどころはどこなのか
    インターフェースを使うと何が嬉しいのか
    上記2点のイメージを持ってもらうのが目的

    View Slide

  7. サンプルプロジェクト
    GitHubで公開しています
    https://github.com/drumath2237/CSharp-Interface-Lecture

    View Slide

  8. インターフェースのおさらい

    View Slide

  9. インターフェースの文法
    interfaceというキーワードで
    作成できる
    実装するクラスの骨を”定義”
    - プロパティ
    - メソッド
    - イベント
    など定義可能
    public interface IHoge
    {
    // プロパティの定義
    int Foo { get; }
    // インスタンスメソッドの定義
    void Bar();
    // イベントの定義
    event Action Boo;
    }

    View Slide

  10. インターフェースの文法
    インターフェースの”実装”
    実装なので
    具体的な実装内容を記述
    定義されたものを不足なく
    実装する必要がある
    class Hoge : IHoge
    {
    public int Foo => 3;
    public event Action Boo;
    public void Bar()
    {
    Console.WriteLine("bar");
    Boo?.Invoke();
    }
    }

    View Slide

  11. 抽象クラスとの違い
    抽象クラスを”継承”し、
    抽象メソッドを”実装”する
    抽象クラスには
    具体的な実装も書ける
    オーバーライドもできる
    メンバの継承もできる
    抽象クラスは
    一つしか継承できない
    (C#の場合)
    public abstract class AbstractHoge
    {
    public abstract void Bar();
    protected virtual void Foo()
    {
    Console.WriteLine("Foo");
    }
    }
    public class Hoge2 : AbstractHoge
    {
    public override void Bar()
    {
    Console.WriteLine("bar");
    }
    protected override void Foo()
    {
    base.Foo();
    Console.WriteLine("overrided");
    }
    }

    View Slide

  12. このインターフェース
    って何に使えるの.........?
    ところで

    View Slide

  13. 色々使えて
    とても便利なんです
    という話をします

    View Slide

  14. 3つに分けてインターフェースの使いどころを解説
    - 「できること」を保証(定義)できる
    - 「不要な機能」を隠蔽できる
    - 「依存先の影響」を受けにくくできる

    View Slide

  15. 3つに分けてインターフェースの使いどころを解説
    - 「できること」を保証(定義)できる
    - 「不要な機能」を隠蔽できる
    - 「依存先の影響」を受けにくくできる

    View Slide

  16. メソッドの強制実装

    View Slide

  17. 動詞を定義する
    個人的なイメージだと
    インターフェースは”動詞”
    IXxxableという名前はよく聞く
    動詞を強制的に実装させる
    →できることの保証
    public interface IMovable
    {
    void GoForward();
    void GoBack();
    void GoRight();
    void GoLeft();
    }
    public interface IFlyable
    {
    void Fly();
    }

    View Slide

  18. 動詞を定義する
    個人的なイメージだと
    インターフェースは”動詞”
    I〇〇ableという名前はよく聞く
    動詞を強制的に実装させる
    →できることの保証
    class Car : IMovable
    {
    //...
    }
    class Bus : IMovable
    {
    //...
    }
    class Airplane: IMovable, IFlyable
    {
    // ...
    }

    View Slide

  19. クラスの分類と多態性
    できることが保証されている
    ○○ができるグループとも見える
    例)
    車やバスや飛行機は操作可能
    (Movableな分類に属する)
    Movableなものならなんでもいい
    ってときに便利
    // IMovable movable = new Car();
    // IMovable movable = new Bus();
    IMovable movable = new Airplane();
    movable.GoForward();

    View Slide

  20. メソッドの強制実装を活用した例
    - IEnumerableインターフェース
    - IDisposableインターフェース
    メソッドの強制実装を活用した例はいくつかある
    どちらもC#が標準で用意しているインターフェース

    View Slide

  21. IEnumerable
    ”列挙できる”ことを保証
    IEnumeratorと組み合わせて定義
    する
    有名な実装先は
    ArrayやList
    foreach文が使えるようになる
    IEnumerable intEnumrable
    = new int[] { 1, 2, 3 };
    IEnumerable strEnumrable
    = new List() { "a", "b", "c" };
    foreach (var num in intEnumrable)
    {
    Console.WriteLine(num);
    }
    foreach (var str in strEnumrable)
    {
    Console.WriteLine(str);
    }

    View Slide

  22. IDisposable
    必ず”破棄する”ことを定める
    Dispose()メソッドを
    呼ぶようにプログラマに指示
    メモリの解放処理や
    入出力のクローズ処理、
    デバイスの動作終了処理
    を実装することが多い
    using文、using宣言が使える
    var buffer = new char[50];
    using (var reader = new StreamReader("file1.txt"))
    {
    reader.Read(buffer, 0, buffer.Length);
    }
    var buffer = new char[50];
    var reader = new StreamReader("file1.txt");
    reader.Read(buffer, 0, buffer.Length);
    reader.Dispose();
    OR
    https://docs.microsoft.com/ja-jp/dotnet/standard/garbage-collection/using-objects
    より引用

    View Slide

  23. 3つに分けてインターフェースの使いどころを解説
    - 「できること」を保証(定義)できる
    - 「不要な機能」を隠蔽できる
    - 「依存先の影響」を受けにくくできる

    View Slide

  24. 情報の隠蔽

    View Slide

  25. 不要な情報は、隠そう
    情報の隠蔽はすごく大事
    不要なデータを見せない・変更させない
    インターフェースを使えば
    関心のある情報のみ見えるように整理できる
    「インターフェース」たる所以なのではないか?

    View Slide

  26. ReactivePropertyとIReadOnlyReactiveProperty
    UniRxの主要な機能である
    ReactiveProperty
    値が変更されたことを受けて
    購読者に通知する機能がある
    これによりMV(R)Pという
    UIのためのアーキテクチャを構築
    // Reactive Propertyの初期化
    var reactiveInt = new ReactiveProperty();
    // 変更されたら通知
    reactiveInt.Subscrive(x =>
    {
    Debug.Log(x);
    });
    // Valueプロパティから値を変更
    reactiveInt.Value = 1;

    View Slide

  27. ReactivePropertyのコード(一部抜粋)
    public interface IReadOnlyReactiveProperty : IObservable
    {
    T Value { get; }
    bool HasValue { get; }
    }
    public interface IReactiveProperty : IReadOnlyReactiveProperty
    {
    new T Value { get; set; }
    }
    public class ReactiveProperty : IReactiveProperty, ...
    {
    public T Value
    {
    get { return value; }
    set { ... }
    }
    }

    View Slide

  28. getterのみ公開する
    ReactivePropertyを
    ReadOnlyとして公開できる
    外部からは変更できないが
    購読できる
    不要な情報の隠蔽により
    安全な実装ができた
    class A
    {
    private ReactiveProperty _intReactiveProperty;
    public IReadOnlyReactiveProperty ROIntReactiveProperty
    => _intReactiveProperty;
    private void Hoge()
    {
    _intReactiveProperty.Value = 10;
    }
    }
    class B
    {
    void Main()
    {
    var a = new A();
    a.ROIntReactiveProperty.Value = 10; // エラー!!!
    }
    }

    View Slide

  29. 3つに分けてインターフェースの使いどころを解説
    - 「できること」を保証(定義)できる
    - 「不要な機能」を隠蔽できる
    - 「依存先の影響」を受けにくくできる

    View Slide

  30. 抽象への依存
    ちょっと難しいかも

    View Slide

  31. インターフェースは抽象的である
    インターフェースは具体的な実装を持たない
    これが重要な性質
    - 具体を差し替える
    - 仮組ができる
    - 依存方向をコントロール
    という観点から解説

    View Slide

  32. 具体の差し替え
    天気を取得するサービスの例
    都市コードを入力したら
    その時の天気を返してくれるAPI
    具体的にどうする?
    - A社の天気予報サービス?
    - B社の天気予報サービス?
    - オレオレ天気予報?
    public enum Weather
    {
    Sunny,
    Rainy,
    Cloudy
    }
    public interface IWeatherService
    {
    Task TryGetWeatherAsync(string cityCode);
    }

    View Slide

  33. A社の天気予報を使おう!
    →実装完了

    View Slide

  34. A社「天気予報やめるわwwwww」
    は?「ワイ」

    View Slide

  35. プログラムは変更がつきもの
    仕様変更や機能追加
    サードパーティ製ライブラリの変更・アプデ
    技術的負債の解消
    プラットフォームのアプデ
    (チームメンバーの変化)

    View Slide

  36. もしA社サービスにべったり依存していたら
    クラスA
    クラスC
    クラスB
    B社サービス
    A社サービス
    変更
    変更
    変更
    実装

    View Slide

  37. もし抽象のみに依存していたら
    クラスA
    クラスC
    クラスB
    B社サービス
    A社サービス
    抽象化された
    サービス
    具体の差し替え 実装

    View Slide

  38. 抽象に依存すると
    変更箇所が少なくなる
    →変更に強くなる

    View Slide

  39. ここまでが具体の差し替え
    ここから仮組について

    View Slide

  40. プログラムの仮組に活用
    たとえば......
    1. プログラムが定期的に動作
    2. サービスAから情報を取得
    3. 情報を分析
    4. デバイス①を情報をもとに動作させる
    サービスAやデバイス①は変更の可能性があるので
    抽象化しておく

    View Slide

  41. プログラムの仮組に活用
    1. プログラムが定期的に動作
    2. 抽象的なサービスから情報を取得
    3. 情報を分析
    4. 抽象的なデバイスを情報をもとに動作させる
    抽象的なサービスや抽象的なデバイスは具体的な実装がない
    だけどインターフェースが定義されていればプログラムは組める

    View Slide

  42. サンプルの一部
    _movableや_serviceは抽象的
    中身が何なのかわからない
    CarなのかBusなのか?
    A社の天気予報なのか?
    public class App
    {
    private readonly IMovable _movable;
    private readonly IWeatherService _service;
    public App(IMovable movable, IWeatherService service)
    {
    _movable = movable;
    _service = service;
    }
    public async Task RunAsync(string cityCode)
    {
    var weather = await _service.TryGetWeatherAsync(cityCode);
    switch (weather)
    {
    case Weather.Rainy:
    _movable.GoForward();
    break;
    case Weather.Cloudy:
    _movable.GoForward();
    break;
    case Weather.Sunny:
    default:
    _movable.GoBack();
    break;
    }
    }
    }

    View Slide

  43. 具体的な実装がなくても
    仮組できる

    View Slide

  44. 他の開発メンバーに
    任せられる

    View Slide

  45. ここまでが仮組について
    ここから依存方向のコントロールについて

    View Slide

  46. 処理の流れと依存方向は?
    new A();
    class A
    {
    public A() { new B(); }
    }
    class B
    {
    public B() { new C(); }
    }
    class C { }

    View Slide

  47. 処理の流れ 依存方向
    A
    C
    B
    AがBを呼ぶ
    BがCを呼ぶ
    A
    C
    B
    AがBに依存
    BがCに依存

    View Slide

  48. 処理の流れと依存方向
    普通に実装すると、同じ方向になることが多い
    Cがサードパーティに依存するコードだったら......?
    AやBがドメインロジックだったとして
    ドメインが末端のロジックに依存することに
    安定すべきコアなロジックが
    変更に柔軟な末端ロジックに依存するのはマズイ
    依存方向を逆転させよう!

    View Slide

  49. 依存方向
    A
    C
    B
    AがBに依存
    BがCに依存
    依存方向
    A
    C
    B
    AがBに依存
    CがICを実装
    B名前空間に依存
    IC
    BがICに依存

    View Slide

  50. 依存方向
    A
    C
    B
    AがBに依存
    BがCに依存
    依存方向
    A
    C
    B
    AがBに依存
    CがICを実装
    B名前空間に依存
    IC
    BがICに依存
    依存性逆転の原則
    依存方向が逆転
    している

    View Slide

  51. 処理の流れと
    依存方向を
    独立(反転)させる

    View Slide

  52. おわりに

    View Slide

  53. インターフェース完全に理解した、その先
    インターフェースを理解することで
    実践的なソフトウェア開発手法の理解につながる
    - 設計(クリーンアーキテクチャなど)
    - DI(依存性注入)
    - テスト
    大規模開発・チーム開発・長期間の開発
    などでは有効な考え方になる

    View Slide

  54. インターフェースは銀の弾丸か
    銀の弾丸ではない
    扱うオブジェクトやファイルが増える
    というデメリット
    小規模・短期間の個人開発なら
    きれいな設計をしなくても良いケースは往々にしてある
    導入が妥当かは見極めるようになりたい

    View Slide

  55. まとめ
    インターフェースは
    - 「できること」を保証(定義)できる
    - 「不要な機能」を隠蔽できる
    - 「依存先の影響」を受けにくくできる
    などを実現する手段として、とても有効

    View Slide

  56. 参考文献
    【C#】インターフェイスの利点が理解できない人は「インターフェイスには3つのタイプがある」ことを理解しよう
    https://qiita.com/yutorisan/items/d28386f168f2f3ab166d
    依存性の逆転のいちばんわかりやすい説明
    https://zenn.dev/naas/articles/c743a3d046fa78
    世界一わかりやすいClean Architecture
    https://www.nuits.jp/entry/easiest-clean-architecture-2019-09-vsuc
    UniRx ReactiveProperty.cs
    https://github.com/neuecc/UniRx/blob/master/Assets/Plugins/UniRx/Scripts/UnityEngineBridge/ReactiveProperty.cs

    View Slide