C#でOpen-Closed Principle(OCP)を説明してみる。
はじめに
自学でデザインパターンや設計を勉強してもなかなか定着しなかったのですが、お客様先(常駐している現場)の上司に教えてもらって一気に理解が進んだので備忘録として残しておこうと思います。
Open-Closed Principle(OCP)とは?
OCPとはSOLID原則の1つであり、「クラスは拡張に対して開かれており、修正(変更)に対しては閉じていなければならない」という考え方です。
つまり「新しい機能を追加する(拡張)場合は既存の機能のことを考える必要なく(それぞれの機能が独立している)、既存機能を修正・変更する際は他に依存しないような作りにする」ということです。
と言ってもサンプルコードがないと!
と言っても言葉だけではやはり理解ができません。。。
そこでサンプルコードを書いてみました。
以下はASP.NET Core コンソールアプリで渡されたアプリケーション引数によって犬か猫の鳴き声を表示させるプログラムです。
OCPに反したコード
namespace ConsoleApp2
{
public class Animal
{
public static string Dog() => "bowwow";
public static string Cat() => "meow";
}
}
using System;
using System.Linq;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
if (!args.Any())
{
Console.WriteLine("アプリケーション引数を入れてください。");
return;
}
string nakigoe;
switch (args[0])
{
case "dog":
nakigoe = Animal.Dog();
Console.WriteLine(nakigoe);
break;
case "cat":
nakigoe = Animal.Cat();
Console.WriteLine(nakigoe);
break;
}
Console.ReadLine();
}
}
}
一見、よく見るパターンです。
僕もよくこんな感じで書いちゃっていましたが、これだと例えば鳥の鳴き声を表示する機能を作る際にAnimalクラスに鳥の鳴き声の関数を、ProgramクラスのSwitchの中に鳥の場合を足していかなければいけません。
このサンプルだと「それくらい余裕でしょ!」といけますが、実際に業務で扱うプログラムはこんなに単純ではないのでミスが増える可能性が高まります。
そこで、OCP原則に基づいたコードに変えてみましょう。
インターフェースを用いて以下のように作成します。
OCP原則に基づいたコード
namespace ConsoleApp1
{
public interface IAnimalService
{
public string Pattern { get; }
public string Nakigoe();
}
public class DogService : IAnimalService
{
public string Pattern => "dog";
public string Nakigoe() => "bowwow";
}
public class CatService : IAnimalService
{
public string Pattern => "cat";
public string Nakigoe() => "meow";
}
}
using System;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
if (!args.Any())
{
Console.WriteLine("アプリケーション引数を入れてください。");
return;
}
var services = new ServiceCollection();
services.AddTransient<IAnimalService, DogService>();
services.AddTransient<IAnimalService, CatService>();
using var provider = services.BuildServiceProvider();
//DIされた内容が配列として渡ってきます
var animalServices = provider.GetServices<IAnimalService>();
var animalService = animalServices
.FirstOrDefault(o => o.Pattern == args[0]) ?? new DogService();
var nakigoe = animalService.Nakigoe();
Console.WriteLine(nakigoe);
Console.ReadLine();
}
}
}
(DIやFirstOrDefaultのデフォルト時にDogServiceとなっているのは一旦置いておきましょう)
こちらではDIで配列として渡ってきたanimalServicesのPatternとアプリケーション引数を比較してサービスを求めています。
"Pattern"はDogServiceクラス、CatServiceクラスでそれぞれ定義しています。
このプログラムだと先ほどと同じように新しく鳥の鳴き声を表示させる機能を作る場合にProgramクラスをいじる必要はありません。
IAnimalServiceインターフェースを継承して鳥のサービスクラスを作るだけでOKです。
(DIコンテナに鳴き声クラスを登録する必要はありますが...)
おわりに
日々の仕様変更に耐えるためにも保守性を意識したプログラムは大切だなと思うのでしっかり理解を深めていきたいところです。
Author And Source
この問題について(C#でOpen-Closed Principle(OCP)を説明してみる。), 我々は、より多くの情報をここで見つけました https://qiita.com/Ooooooomin_365/items/4fc7a269d5598931f0ec著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .