気づかなくても、私たちは皆、いくつかのパターンでコードを書いています。私は、SOLIDの原則のいくつかと、これらの原則を現実の世界にどのように適用するかを本当に理解しようとしています。
「 D 」に苦労しています。
依存性逆転と依存性注入を混同することがあります。それは、抽象化(IE:interfaces)に応じて物事を維持する限り、完了したことを意味しますか?
誰かがそれを説明する小さなC#の例さえ持っていますか?
ありがとう。
気づかなくても、私たちは皆、いくつかのパターンでコードを書いています。私は、SOLIDの原則のいくつかと、これらの原則を現実の世界にどのように適用するかを本当に理解しようとしています。
「 D 」に苦労しています。
依存性逆転と依存性注入を混同することがあります。それは、抽象化(IE:interfaces)に応じて物事を維持する限り、完了したことを意味しますか?
誰かがそれを説明する小さなC#の例さえ持っていますか?
ありがとう。
Mark Seemanのブログをご覧になるか、さらに良いことに、彼の本を購入してください。DIだけではありません。簡単なサンプルを使ってみてください。しかし、それは理解していると主張する多くの人が理解していないので、よく学ぶ価値がある主題です。
そうは言っても、これは非常に簡単な例です。私が理解しているように、用語
は制御の反転と依存性注入です。new
制御の反転とは、通常はキーワードを介して、依存関係自体を制御するクラスとは対照的に、クラスの依存関係の制御を他のクラスに与えるという事実を指します。この制御は、依存性注入を介して実行されます。依存性注入では、クラスに依存性が与えられるか、注入されます。これは、IoCフレームワークまたはコード(Pure DIと呼ばれる)で実行できます。インジェクションは、クラスのコンストラクターで、プロパティを介して、またはメソッドのパラメーターとして実行できます。依存関係はどのタイプでもかまいません。抽象的である必要はありません。
ドープしていないツールドフランスの勝者を一覧表示するクラスは次のとおりです。
class CleanRiders
{
List<Rider> GetCleanRiders()
{
var riderRepository = new MsSqlRiderRepository();
return riderRepository.GetRiders.Where(x => x.Doping == false);
}
}
このクラスはに依存していMsSqlRiderRepository
ます。クラスは、インスタンスの作成を制御します。問題は、この依存関係に柔軟性がないことです。OracleRiderRepository
aまたはに変更するのは難しいTestRiderRepository
です。
IoCとDIは、これを解決します。
class CleanRiders
{
private IRiderRepository _repository;
public CleanRiders(IRiderRepository repository)
{
_repository = repository;
}
List<Rider> GetCleanRiders()
{
return _repository.GetRiders.Where(x => x.Doping == false);
}
}
現在、クラスはインターフェイスのみに依存しています。依存関係の制御はクラスの作成者に委ねられており、コンストラクターを介して注入する必要があります。
void Main()
{
var c = new CleanRiders(new MsSqlRepository());
var riders = c.GetRiders();
}
間違いなく、より柔軟で、テスト可能で、SOLIDアプローチです。
S:単一責任の原則
次のコードには問題があります。「自動車」クラスには、2つの異なる責任があります。1つは車のモデルの管理、アクセサリの追加などです。次に、2つ目の責任は車の販売/リースです。これはSRPを壊します。これらの2つの責任は別です。
public Interface ICarModels {
}
public class Automobile : ICarModels {
string Color { get; set; }
string Model { get; set; }
string Year { get; set; }
public void AddAccessory(string accessory)
{
// Code to Add Accessory
}
public void SellCar()
{
// Add code to sell car
}
public void LeaseCar()
{
// Add code to lease car
}
}
この問題を修正するには、Automobileクラスを分割し、個別のインターフェイスを使用する必要があります。
public Interface ICarModels {
}
public class Automobile : ICarModels {
string Color { get; set; }
string Model { get; set; }
string Year { get; set; }
public void AddAccessory(string accessory)
{
// Code to Add Accessory
}
}
public Interface ICarSales {
}
public class CarSales : ICarSales {
public void SellCar()
{
// Add code to sell car
}
public void LeaseCar()
{
// Add code to lease car
}
}
インターフェイスとクラスを設計するときは、責任について考えてください。クラスの変更には何が含まれますか?クラスを最も単純な形式に分割します...ただし、(アインシュタインが言うように)単純な形式ではありません。
O:オープン/クローズド原則
要件が変更され、処理のためにさらにタイプが追加された場合、クラスは変更を必要としないように十分に拡張可能である必要があります。新しいクラスを作成して処理に使用できます。言い換えれば、クラスは拡張可能でなければなりません。私はこれを「If-Type」の原則と呼んでいます。コードにif(type == ....)がたくさんある場合は、コードを個別のクラスレベルに分割する必要があります。
この例では、ディーラーの車種の合計価格を計算しようとしています。
public class Mercedes {
public double Cost { get; set; }
}
public class CostEstimation {
public double Cost(Mercedes[] cars) {
double cost = 0;
foreach (var car in cars) {
cost += car.Cost; } return cost; }
}
しかし、ディーラーはメルセデスを運ぶだけではありません!これは、クラスがもう拡張できない場所です!他の車種のコストも合計したい場合はどうなりますか?!
public class CostEstimation {
public double Cost(object[] cars)
{
double cost = 0;
foreach (var car in cars)
{
if (car is Mercedes)
{
Mercedes mercedes = (Mercedes) car;
cost += mercedes.cost;
}
else if (car is Volkswagen)
{
Volkswagen volks = (Volkswagen)car;
cost += volks.cost;
}
}
return cost;
}
}
今は壊れています!ディーラーロットのすべての車種について、クラスを変更し、別のifステートメントを追加する必要があります。
それでは修正しましょう:
public abstract class Car
{
public abstract double Cost();
}
public class Mercedes : Car
{
public double Cost { get; set; }
public override double Cost()
{
return Cost * 1.2;
}
}
public class BMW : Car
{
public double Cost { get; set; }
public override double Cost()
{
return Cost * 1.4;
}
}
public class Volkswagen : Car
{
public double Cost { get; set; }
public override double Cost()
{
return Cost * 1.8;
}
}
public class CostEstimation {
public double Cost(Car[] cars)
{
double cost = 0;
foreach (var car in cars)
{
cost += car.Cost();
}
return cost;
}
}
ここで問題は解決しました!
L:リスコフの置換原則
SOLIDのLは、リスコフの原則を指します。オブジェクト指向プログラミングの継承の概念は、派生クラスが基本クラスの動作を変更できない場合に固めることができます。リスコフの原則の実際の例に戻ります。しかし今のところ、これは原則そのものです。
T->ベース
ここで、T[派生クラス]はBaseの動作を改ざんしてはなりません。
I:インターフェース分離の原理
C#のインターフェイスは、インターフェイスを実装するクラスによって実装される必要があるメソッドをレイアウトします。例えば:
Interface IAutomobile {
public void SellCar();
public void BuyCar();
public void LeaseCar();
public void DriveCar();
public void StopCar();
}
このインターフェース内では、2つのグループのアクティビティが実行されています。1つのグループはセールスマンに属し、別のグループはドライバーに属します。
public class Salesman : IAutomobile {
// Group 1: Sales activities that belong to a salesman
public void SellCar() { /* Code to Sell car */ }
public void BuyCar(); { /* Code to Buy car */ }
public void LeaseCar(); { /* Code to lease car */ }
// Group 2: Driving activities that belong to a driver
public void DriveCar() { /* no action needed for a salesman */ }
public void StopCar(); { /* no action needed for a salesman */ }
}
上記のクラスでは、DriveCarメソッドとStopCarメソッドを実装する必要があります。セールスマンにとって意味がなく、そこに属していないもの。
public class Driver : IAutomobile {
// Group 1: Sales activities that belong to a salesman
public void SellCar() { /* no action needed for a driver */ }
public void BuyCar(); { /* no action needed for a driver */ }
public void LeaseCar(); { /* no action needed for a driver */ }
// Group 2: Driving activities that belong to a driver
public void DriveCar() { /* actions to drive car */ }
public void StopCar(); { /* actions to stop car */ }
}
同じように、SellCar、BuyCar、LeaseCarの実装を余儀なくされています。明らかにDriverクラスに属していないアクティビティ。
この問題を修正するには、インターフェースを2つに分割する必要があります。
Interface ISales {
public void SellCar();
public void BuyCar();
public void LeaseCar();
}
Interface IDrive {
public void DriveCar();
public void StopCar();
}
public class Salesman : ISales {
public void SellCar() { /* Code to Sell car */ }
public void BuyCar(); { /* Code to Buy car */ }
public void LeaseCar(); { /* Code to lease car */ }
}
public class Driver : IDrive {
public void DriveCar() { /* actions to drive car */ }
public void StopCar(); { /* actions to stop car */ }
}
インターフェイスの分離!
D:依存性逆転の原則
問題は、誰が誰に依存するのかということです。
従来のマルチレイヤーアプリケーションがあるとしましょう。
コントローラレイヤー->ビジネスレイヤー->データレイヤー。
コントローラーから、従業員をデータベースに保存するようにビジネスに指示するとします。ビジネスレイヤーは、データレイヤーにこれを実行するように要求します。
そこで、コントローラーの作成に着手しました(MVCの例)。
public class HomeController : Controller {
public void SaveEmployee()
{
Employee empl = new Employee();
empl.FirstName = "John";
empl.LastName = "Doe";
empl.EmployeeId = 247854;
Business myBus = new Business();
myBus.SaveEmployee(empl);
}
}
public class Employee {
string FirstName { get; set; }
string LastName { get; set; }
int EmployeeId { get; set; }
}
次に、ビジネスレイヤーには次のものがあります。
public class Business {
public void SaveEmployee(Employee empl)
{
Data myData = new Data();
myData.SaveEmployee(empl);
}
}
データレイヤーで接続を作成し、従業員をデータベースに保存します。これは、従来の3層アーキテクチャです。
それでは、コントローラーを改善しましょう。コントローラ内にSaveEmployeeメソッドを配置する代わりに、すべてのEmployeeアクションを処理するクラスを作成できます。
public class PersistPeople {
Employee empl;
// Constructor
PersistPeople(Employee employee) {
empl = employee;
}
public void SaveEmployee() {
Business myBus = new Business();
myBus.SaveEmployee();
}
public Employee RetrieveEmployee() {
}
public void RemoveEmployee() {
}
}
// Now our HomeController is a bit more organized.
public class HomeController : Controller {
Employee empl = new Employee();
empl.FirstName = "John";
empl.LastName = "Doe";
empl.EmployeeId = 247854;
PersistPeople persist = new Persist(empl);
persist.SaveEmployee();
}
}
それでは、PersistPeopleクラスに集中しましょう。これは、Employeeクラスとハードコーディングされ、緊密に結合されています。建設業者の従業員を受け入れ、ビジネスクラスをインスタンス化して保存します。「従業員」ではなく「管理者」を保存したい場合はどうなりますか?現在、PersistクラスはEmployeeクラスに完全に「依存」しています。
この問題を解決するために「依存性逆転」を使用してみましょう。ただし、その前に、EmployeeクラスとAdminクラスの両方から派生するインターフェイスを作成する必要があります。
Interface IPerson {
string FirstName { get; set; }
string LastName { get; set; }
int EmployeeId { get; set; }
}
public class Employee : IPerson {
int EmployeeId;
}
public class Admin : IPerson {
int AdminId;
}
public class PersistPeople {
IPerson person;
// Constructor
PersistPeople(IPerson person) {
this.person = person;
}
public void SavePerson() {
person.Save();
}
}
// Now our HomeController is using dependency inversion:
public class HomeController : Controller {
// If we want to save an employee we can use Persist class:
Employee empl = new Employee();
empl.FirstName = "John";
empl.LastName = "Doe";
empl.EmployeeId = 247854;
PersistPeople persist = new Persist(empl);
persist.SavePerson();
// Or if we want to save an admin we can use Persist class:
Admin admin = new Admin();
admin.FirstName = "David";
admin.LastName = "Borax";
admin.EmployeeId = 999888;
PersistPeople persist = new Persist(admin);
persist.SavePerson();
}
}
したがって、要約すると、Persistクラスは依存しておらず、Employeeクラスにハードコーディングされていません。Employee、Adminなど、任意の数のタイプを取ることができます。渡されたものを保存するためのコントロールは、HomeControllerではなくPersistクラスにあります。Persistクラスは、渡されたもの(Employee、Adminなど)を保存する方法を認識します。これで、制御が反転され、Persistクラスに渡されます。SOLIDの原則の優れた例については、このブログを参照することもできます。
参照:https ://darkwareblog.wordpress.com/2017/10/17/
これがお役に立てば幸いです。
先日、同僚に説明しようとしていたのですが、その過程で実際に自分でもその概念を理解していました。特に、実生活での依存性逆転の実例を思いついたとき。
物語
車の運転手が車に依存していたと想像してみてください。1台の車しか運転できません。これはかなり悪いでしょう:
この場合、依存関係の方向は次のとおりです。Driver => Car(DriverオブジェクトはCarオブジェクトに依存します)。
ありがたいことに、実際には、各車には「ステアリングホイール、ペダル、ギアシフター」というインターフェースがあります。ドライバーはもはや車に依存しないので、ドライバーはどの車でも運転できます。
現在、TheDriverはICarインターフェースに依存していますが、 TheCarもICarインターフェースに依存しています-依存関係は反転しています:
私は他の人のような専門家ではありませんが、DIPを概念的に説明することを試みます。DIPの中心となるのは、インターフェースへのプログラムです。つまり、高レベルのクラスは抽象化に依存し、低レベルのクラスも抽象化に依存します。例えば
サムスン、アップル、ノキアなどと呼ばれる抽象化を定義するとしますPhoneVendor
。コードについて申し訳ありませんが、しばらくJavaを記述していないため、構文エラーが発生する可能性がありますが、それでも概念についてです。
public abstract class PhoneVendor {
/**
* Abstract method that returns a list of phone types that each vendor creates.
*/
public abstract Vector getPhones(){ }
}
public class Samsung extends PhoneVendor{
public Vector getPhones(){ // return a list of phones it manufactures... }
}
public class PhoneFinder{
private PhoneVendor vendor;
public PhoneFinder(PhoneVendor vendor){ this.vendor = vendor;}
/**
*for example just return a concatnated string of phones
*/
public string getPhoneTypes(){
Vector ListOfPhones = PhoneVendor.getPhones();
return ListOfPhones;
}
}
ご覧のとおり、PhoneFinderクラスは、PhoneVendorの実装ではなく抽象化に依存しています。そして、抽象化を実装する基本クラスは、それを使用する高レベルのクラスから切り離されています。これにより、PhoneFinderは実装ではなく抽象化に依存するため、新しい低レベルクラスを追加しても、以前に記述されたコードが破損することはありません。