Thứ Hai, 24 tháng 11, 2014

Windsor ISubDependencyResolver example

Given the following service:
public class MySuperService
{
  public MySuperService(string connectionString)
  {
    //..
  }
}
I want Windsor automatically to look at the web.config or app.config settings and if there is a configuration with key equals to the parameter name (i.e. “connectionString”) to inject automatically the value when constructing MySuperService instance.
The infrastructure code is this:
public class DependenciesFromAppSettings : AbstractFacility
{
    protected override void Init()
    {
        var dic = ConfigurationManager
            .AppSettings
            .AllKeys
            .ToDictionary(k => k, k => ConfigurationManager.AppSettings[k]);

        Kernel.Resolver.AddSubResolver(new DependenciesFromAppSettingsResolver(dic));
    }
}

public class DependenciesFromAppSettingsResolver : ISubDependencyResolver
{
    private readonly IDictionary webConfig;

    public DependenciesFromAppSettingsResolver(IDictionary webConfig)
    {
        this.webConfig = webConfig;
    }

    public object Resolve(
        CreationContext context, 
        ISubDependencyResolver contextHandlerResolver, 
        ComponentModel model, 
        DependencyModel dependency)
    {
        return webConfig[dependency.DependencyKey];
    }

    public bool CanResolve(
        CreationContext context, 
        ISubDependencyResolver contextHandlerResolver, 
        ComponentModel model, 
        DependencyModel dependency)
    {
        return dependency.DependencyType == DependencyType.Parameter 
            && webConfig.ContainsKey(dependency.DependencyKey);
    }
}
And the usage is very straightforward:
[TestFixture]
public class DependenciesFromAppSettingsTests
{
  [Test]
  public void CanInjectSettingsFromAppConfig()
  {
    var container = new WindsorContainer();

    container.AddFacility<DependenciesFromAppSettings>();
    container.Register(Component.For<MySuperService>());

    var superInstance = container.Resolve<MySuperService>();
    superInstance.ConnectionString
                 .Should().Be.EqualTo("sample conn string");
  } 
}

Chủ Nhật, 23 tháng 11, 2014

What is SOLID? 5 nguyên tắc cơ bản trong programming – SOLID

SOLID là 5 nguyên tắc đầu tiên và cơ bản mà bất cứ programmer nào cũng cần phải hiểu rõ:
  1. S – SRP – Single Responsibility Principle - class should have only a single responsibility (i.e. only one potential change in the software's specification should be able to affect the specification of the class)
  2. O – OCP – Open/Closed Principle -  software entities … should be open for extension, but closed for modification.
  3. L – LSP – Liskov Substitution Principle - objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program
  4. I – ISP – Interface Segregation Principle - many client-specific interfaces are better than one general-purpose interface
  5. D – DIP – Dependency Inversion Principle - one should “Depend upon Abstractions. Do not depend upon concretions
Những nguyên tắc này tuy rất khó nhớ tên nhưng ít nhiều ta đều tiếp xúc trong công việc hàng ngày. Vì vậy nhận ra khi nào ta đang follow nguyên tắc gì sẽ rất có lợi cho công việc.
Ý nghĩa của từng nguyên tắc như sau:
  1. SRP

    – a piece of software chỉ nên thực hiện 1 mục đích, 1 trách nhiệm duy nhất. Về cơ bản, các đơn vị cấu trúc của program gồm có:
    • Statement
    • Code block
    • Method/function
    • Class/Interface
    • Module
    • Project/Library
    • Solution
    Đối với mỗi level, chúng ta cần phải rất rõ ràng về công việc mà chúng thực hiện. Điều này giúp cho code dễ đọc, program dễ hiểu và dễ maintain. Sau khi viết code xong, khi đọc lại hoặc khi ng khác đọc sẽ dễ dàng follow đc vấn đề.
    Ví dụ 1:
    1
    Console.WriteLine("You are at the index " + i++);
    Dòng code trên làm 2 việc: in ra index hiện tại và tăng nó lên 1. Đối với ng viết, do đã là thói quen nên họ dễ dàng nhận ra giá trị của biến i sau dòng này. Nhưng đối với ng khác, rất dễ xảy ra nhầm lẫn.
    Ví dụ 2:
    1
    2
    3
    4
    5
    6
    7
    public void Insert(Entity obj) {
      using(var ctx = new MyContext()) {
         ctx.Entities.InsertOnSubmit(obj);
         ctx.SubmitChanges();
         _messageService.TellSomebody("new object inserted.");
      }
    }
    Đoạn code trên có 2 vấn đề:
    1. Trong using block, thực hiện 2 việc là insert object và gọi messageService. Mặc dù code chạy bình thường nhưng đây vẫn là bad practice, messageService cần phải nằm ngoài using ctx.
    2. Trong function Insert thực hiện 2 nhiệm vụ khác nhau. Nếu trong tương lai phát sinh ra nhu cầu Insert nhưng ko kèm notify cho messageService thì function này sẽ phải sửa lại (cùng với tất cả những nơi đã dùng tới nó)
    Cứ tiếp tục như thế, đôi khi chúng ta cảm thấy sẽ “tiện lợi” ghi gộp nhiều thứ lại với nhau. Nhưng thực tế sẽ tạo ra những vấn đề tiềm ẩn cho sau này (technical debt)
  2. OCP

    open for extension but close for modification? tức là sao?
    Nôm na là ta phải thiết kế software sao cho nó có thể mở rộng dễ dàng mà ko cần phải đập đi làm lại. Còn gì bực bội hơn khi được giao viết thêm functionality cho 1 hệ thống mà phát hiện ra cần phải sửa rất nhiều mới support đc chức năng ABC? Điều này cũng giống như thiết kế 1 chiếc xe hơi với các bộ phận tiêu chuẩn có thể dễ dàng thay thế hoặc nâng cấp mà ko cần phải chế tạo lại cả chiếc xe.
    Nguyên tắc này thường đc triển khai dựa trên tính kế thừa và tính đa hình của OOP như trong sơ đồ sau:
    superhero
    Ở đây tôi đã define Superman và Spiderman, và method Fight của 2 entity này đã fixed và không thay đổi được, tức là Closed. Vậy hệ thống có Open hay không? Làm gì để tùy chỉnh?
    • có thể extend và override method của class cũ, chẳng hạn tạo class AmazingSpiderman -> Spiderman với khả năng Fight more amazing
    • extend từ Abstract class và implement abstract method Fight
    Như vậy hệ thống đảm bảo đc tính OCP.
  3. LSP

    có khi nào bạn thấy ko hài lòng về 1 property nào đó của 1 class và viết 1 class con để modify property đó? chẳng hạn property của base class chỉ trả về integer từ 1..10, bạn subclass để nó trả về 1..100? Như vậy là bạn đang violate nguyên tắc LSP.
    Lý do: khi đoạn code nào đó sử dụng đến property này với understanding là nó sẽ trả về 1..10, sẽ bị surprised khi nó trả về 1..100 và ko thể handle từ 11..100
    Ví dụ sau ko hoàn toàn về LSP nhưng cũng cho thấy sự vi phạm tính đa hình của class:
    1
    2
    3
    ISuperHero hero = HeroFactory.GetSuperHero("IronMan");
    hero.Fight();
    (hero as IronMan).FireBeam();
    Đoạn code trên đã phải convert đối tượng về một subtype để thực hiện method cần thiết.
  4. ISP

    khi bạn implement 1 interface mà có những method bạn ko dùng đến, đó là dấu hiệu của problem.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    interface IPlayer {
      void Run();
      void Shoot();
      void Catch();
    }
    class Striker : IPlayer {
      public void Run(){...}
      public void Shoot(){...}
      public void Catch(){...}
    }
    Có thể thấy Striker sẽ ko implement đc method Catch. Và nếu ta gọi Player.Catch() có thể sẽ dẫn đến runtime expception. Vì vậy việc class Striker ko thể implement đc method Catch cho thấy 1 technical debt.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    interface IPlayer {
      void Run();
    }
    interface IAttacking : IPlayer {
      void Shoot();
    }
    interface IGoalkeeping {
      void Catch();
    }
    class Striker : IAttacking {
      public void Run(){...}
      public void Shoot(){...}
    }
    class Goalkeeper : IGoalkeeping {
      public void Run(){...}
      public void Catch();
    }
    Như vậy, rõ ràng mọi method trong class đều có ý nghĩa rất rõ ràng, ko lỗi.
  5. DIP

    – hmm… đây là 1 nguyên tắc về decoupled, thuộc về design nhiều hơn là coding, vì vậy tương đối khó để nắm bắt. Có thể liên tưởng qua ví dụ thực tế như sau:
    Ex: giả sử A làm ăn với B, A mua dịch vụ do B cung cấp. Đôi khi B ko thực hiện đúng trách nhiệm của mình, A mặc dù bực bội về việc đó, nhưng đã ký kết làm ăn nên ko thể đổi được, và cũng ko có đủ khả năng tìm đối tác khác. Như vậy A đã phụ thuộc chặt chẽ vào B.
    Tuy nhiên, nếu ban đầu A ko trực tiếp làm việc với B, mà thông qua trung gian X. A đưa ra yêu cầu dành cho nhà phân phối của mình và X sẽ tìm người đáp ứng được nhu cầu đó, giả sử ban đầu là B. Nếu sau đó B ko thực hiện đúng trách nhiệm, X sẽ thay B bằng nhà cung cấp C. Như vậy việc sử dụng dịch vụ của A sẽ ko bị gián đoạn và A cũng ko cần làm việc trực tiếp với B hay C.
    Điều này minh họa trong code như sau:
    1
    2
    3
    4
    5
    6
    7
    class Messi {
      public void BringTheBallForward() {
        var supplier = new Xavi();
        messi.Ball = supplier.PassTheBall();
        messi.Dribble();
      }
    }
    Ta có thể thấy Messi phụ thuộc hoàn toàn vào Xavi. Có thể sửa lại như sau:
    1
    2
    3
    4
    5
    6
    7
    class Messi {
      public IMidfielder Midfielder {}
      public void BringTheBallForward() {
        messi.Ball = Midfielder.PassTheBall();
        messi.Dribble();
      }
    }
    Như vậy program có thể điều chỉnh và inject Midfielder vào, giảm sự lệ thuộc giữa các class với nhau.
Tóm lại, các principle trên có tác dụng chính là giúp chúng ta nắm được và dự đoán được các vấn đề tiềm ẩn của program thông qua các dấu hiệu vi phạm 5 principle này để giải quyết chúng 1 cách triệt để.