Pengenalan Dependency Injection atau Inversion of Control

9 views
Skip to first unread message

Ikhwan Hayat

unread,
Jan 8, 2009, 4:07:19 AM1/8/09
to altn...@googlegroups.com
== Dependency Injection 101 ==

Lihat class/interface yang berikut:

//--------------------------------------------------------------

public interface IProfileRepository
{
string GetFullName(Guid id);
}

public class ProfileRepository : IProfileRepository
{
public string GetFullName(Guid id)
{
// Get from database
}
}

public interface IPaymentService
{
void MakePayment(Guid profileId, decimal amount);
}

//--------------------------------------------------------------

public class PaymentService : IPaymentService
{
private IProfileRepository profileRepository;

private string gatewayUrl = "https://gateway.com/payment.do";
private string redirectUrl = "~/Payments/Done.aspx";
private string adminEmail = "pay...@altnetmy.com";

public PaymentService(IProfileRepository profileRepository)
{
this.profileRepository = profileRepository;
}

public void MakePayment(Guid profileId, decimal amount)
{
var name = profileRepository.GetFullName(profileId);

//
// Go to payment gateway,
// sending name, amount, redirectUrl, and adminEmail
//
}
}

//--------------------------------------------------------------

Kita ada PaymentService utk buat online payment. PaymentService
bergantung (depends) kpd UserRepository utk dapatkan nama pengguna.

Classes ni di design dgn konsep Single Responsibility Principle dlm
kepala. Hasilnya ada byk class berasingan dicipta, dan classes ni akan
dependant pada satu sama lain utk berfungsi dgn betul.

Jadi, bila nak pakai, biasanya kita akan buat mcm ni:

var repoProfile = new ProfileRepository();
var svcPayment = new PaymentService(repoProfile);
svcPayment.MakePayment(myProfileId, 1000.0m);

Masalahnya, setiap kali kita nak guna PaymentService ni kita kena
instantiate ProfileRepository jugak. Kalau ProfileRepository ada
dependency jugak, maka kita kena instantiate semua dependency tu. Jadi
sgt leceh.

Maka kita gunakan Inversion of Control (IoC) atau nama lainnya
Dependency Injection (DI) frameworks utk membantu kita. Framework2 ini
jugak dipanggil IoC/DI containers sbb dianalogikan sbg bekas atau
bakul utk menyimpan object kita.

Object kita akan dicipta dan kemudiannya disimpan dlm container ini.
Dependencies yg ada akan diselesaikan oleh container ni.

Menggunakan DI, kita buat mcm ni:

// We are using Castle Windsor
var svcPayment = Container.Resolve<IProfileRepositoy>();
svcPayment.MakePayment(myProfileId, 1000.0m);

Cuma mintak pada container apa class yg kita mahu. Mana container ni
tahu mcm mana nak contruct/instantiate class ni? Kita kena la bagitahu
dia.

Kalau Windsor, semasa application start, kita akan create a Container
object, dan passkan satu XML yg memberitahu mcm mana object2 kita akan
di bina.

<component
id="ProfileRepository"
service="AltNetMy.IProfileRepository, AltNetMy"
type="AltNetMy.ProfileRepository, AltNetMy">
</component>

<component
id="PaymentService"
service="AltNetMy.IPaymentService, AltNetMy"
type="AltNetMy.PaymentService, AltNetMy">
<parameters>
<profileRepository>${ProfileRepository}</profileRepository>
</parameters>
</component>

Itulah serba sedikit tentang DI/IoC. Nampak complex? Jika digunakan
dlm application yg kecik, mmg menyusahkan je. Tapi kalau digunakan dlm
application yg ada berpuluh/ratus classes, it's like a heaven sent.


== Encourage Separation of Interface and Implementation ==

Katakan, semasa testing atau development, kita tak mahu ambik Full
Name dari database, tapi nak ProfileRepository utk pulangkan satu
string "Fulan bin Fulan".

Luckily, sbg OO developer yg baik, kita dah pisahkan interface dan
implementation. Jadi kita buat saja satu lagi implementation utk
MockProfileService.

public class MockProfileRepository : IProfileRepository
{
public string GetFullName(Guid id)
{
// Just return same name for every id
return "Fulan bin Fulan";
}
}

Semasa testing, kita cuma tukarkan implementation ini dalam XML config tadi:

<component
id="ProfileService"
service="AltNetMy.IProfileService, AltNetMy"
type="AltNetMy.MockProfileService, AltNetMy"> <!--change here-->
</component>

Walla, we got a mocked-up class, tanpa perlu tukar apa2 pada client
codes. Benda ni lebih pada konsep separation of interface and
implementation. Penggunaan DI/IoC container menggalakkan kita utk
mencapai konsep ni sebab ia membuatkannya mudah nak dibuat.

Bayangkan pula kalau kemudian hari, kita guna DB tapi juga LDAP utk
simpan profile kita, atau mungkin kita ada satu separate system utk
profile, jadi kita panggil web service utk dapatkan full name tadi.

Jadi kita boleh buat LdapProfileRepository dan
RemoteWebServiceProfileRepository yg extends dari IProfileRepository,
dan tukar implementation mana yg kita nak pakai dlm DI/IoC container
configuration.


== IoC/DI Container As A Configurable Setup ==

Korang tentu perasan beberapa data yg di hardcoded dlm class
PaymentService, iaitu gatewayUrl, redirectUrl, dan adminEmail.

Sebagai developer yg baik juga, kita tahu benda2 mcm ni sepatutnya
diletakkan dalam configuration file atau sebagainya, supaya mudah kita
kalau nak tukar kemudian hari.

Beberapa cara yg boleh digunakan ialah letak dalam .INI file (cara
lama), atau letak dlm appSettings dalam app.config atau web.config.

Kalau letak dlm appSettings boleh jadi berterabur kalau banyak sgt.
Nak jadikan cantik kita boleh buat custom configSection yg kadang2
boleh jadi pain in the neck!

Sebenarnya, cara mudahnya ialah kita gunakan configuration utk IoC/DI tadi.

Tukarkan constructor utk PaymentService jadi mcm ni:

public PaymentService(IProfileRepository profileRepository,
string gatewayUrl,
string redirectUrl,
string adminEmail)
{
this.profileRepository = profileRepository;
this.gatewayUrl = gatewayUrl;
this.redirectUrl = redirectUrl;
this.adminEmail = adminEmail;
}

...dan ubah XML config kita jadi mcm ni:

<component
id="PaymentService"
service="AltNetMy.IPaymentService, AltNetMy"
type="AltNetMy.PaymentService, AltNetMy">
<parameters>
<gatewayUrl>https://verysecuregateway.com/payment.do</gatewayUrl>
<redirectUrl>~/Payments/Done.aspx</redirectUrl>
<adminEmail>pay...@alt.net.my</adminEmail>
</parameters>
</component>

Walla (lagi sekali). Kita dah jadikan XML config tadi bukan sahaja utk
menguruskan dependency kita, tapi juga utk menyimpan configuration yg
mmg biasa kita letak dlm external file.

Ikhwan Hayat

unread,
Jan 8, 2009, 4:18:20 AM1/8/09
to altn...@googlegroups.com
Pembetulan:

1) Pada line 114-118

<component
id="ProfileService"
service="AltNetMy.IProfileService, AltNetMy"
type="AltNetMy.MockProfileService, AltNetMy"> <!--change here-->
</component>

sepatutnya

<component
id="ProfileRepository"
service="AltNetMy.IProfileRepository, AltNetMy"

type="AltNetMy.MockProfileRepository, AltNetMy"> <!--change here-->
</component>

2) Pada line 154-163

<component
id="PaymentService"
service="AltNetMy.IPaymentService, AltNetMy"
type="AltNetMy.PaymentService, AltNetMy">
<parameters>
<gatewayUrl>https://verysecuregateway.com/payment.do</gatewayUrl>
<redirectUrl>~/Payments/Done.aspx</redirectUrl>
<adminEmail>pay...@alt.net.my</adminEmail>
</parameters>
</component>

Korang perasan <profileRepository> dah hilang dari <parameters>? bukan
kesilapan sebenarnya, tapi kalau kita tak specify satu parameter mcm
ni, Windsor akan secara magic cari object yg diperlukan di antara
object2 yg dia dah simpan. Oleh sbb kita dah configure
ProfileRepository, maka Windsor akan pandai gunakan dia.

ron

unread,
Jan 8, 2009, 8:21:19 AM1/8/09
to altn...@googlegroups.com
Apa yg saya nampak cara ni lebih kurang sama dgn Spring IOC, still menggunakan web.config.

Contoh yg saya biasa buat

<spring>
    <context>
      <resource uri="config://spring/objects"/>
    </context>
    <objects xmlns="http://www.springframework.net">
      <description>IoC features.</description>

      <object id="MyAppDM" type="My.Application.TNTEntry.Services.Service.MyAppDM,TNTEntry.Services">
        <property name="IMyAppServ" ref="IMyAppServ"/>
      </object>
      <object id="IMyAppServ" type="My.Application.TNTEntry.Services.MyAppManagement,TNTEntry.Services"/>

      <object id="TntDM" type="My.Application.TNTEntry.Services.Service.TntDM,TNTEntry.Services">
        <property name="ITntServ" ref="ITntServ"/>
      </object>
      <object id="ITntServ" type="My.Application.TNTEntry.Services.TNTManagement,TNTEntry.Services"/>

    </objects>
  </spring>

Jadi di UI akan panggil MyAppDM object disini yg dipanggil dari Services Layer indirectly, disini lah berlakunya loosely coupling, dan saya lebih biasa menggunakan setter injection berbanding constructor. Satu lagi Mock Object saya hanya buat untuk Unit Testing sahaja, lepas ni ingat nak try NUnit mock pulak berbanding custom mock class object.

Boleh bagitau kelebihan Windsor berbanding Spring?

Thanks

 



2009/1/8 Ikhwan Hayat <ikhwa...@gmail.com>

Ikhwan Hayat

unread,
Jan 8, 2009, 10:48:27 AM1/8/09
to altn...@googlegroups.com
> saya lebih biasa menggunakan setter injection berbanding constructor.

Prinsip aku, kalau kalau dependency jenis mesti ada, maksudnya tak
boleh default value je, aku suka letak dlm constructor. Kalau kurang
penting, mcm gatewayUrl, redirectUrl, dan adminEmail dlm
PaymentService tu, aku letak sbg property. Contohnya, kita ubah code
kita jadi:

public class PaymentService
{
public string GatewayUrl { get; set; }
public string RedirectUrl { get; set; }
public string AdminEmail { get; set; }

public PaymentService(IProfileRepository profileRepository)
{
this.profileRepository = profileRepository;

// Set default values
this.GatewayUrl = "https://gateway.com/payment.do";
this.RedirectUrl = "~/Payments/Done.aspx";
this.AdminEmail = "pay...@altnetmy.com";
}

Dan yg bestnya guna Windsor ni, kita tak perlu gunakan config yg sama
di atas tanpa ubah apa2, sbb Windsor tak membezakan antara contructor
atau property injection. Yg dlm tag <parameters> tu, dia akan cari
constructor parameters dulu, kemudian dia akan cari property.

> Satu lagi Mock Object saya hanya buat untuk Unit Testing sahaja,

Kadang2 mcm katakan nak test UI sekali, tapi ada dependency pada web
service lain yg maybe susah nak setup utk test environment, kita boleh
buatkan mock utk tu. Byk membantu jugak cara ni. Boleh je kalau mock
object tu create guna Rhino Mock, Moq, etc, tapi tak pasti mcm mana
nak nak gunakan sekali dgn IoC tanpa buat custom mock class.

> Boleh bagitau kelebihan Windsor berbanding Spring?

Beberapa yg aku nampak la (probably just trivial), dan aku pun tak
mahir dgn Spring:

1) Windsor banyak magic. Contoh mcm kat atas, kita tak specify
parameter, dia boleh infer object mana kita nak pakai. First2 dulu aku
ada fikir gak kalau byk magic mcm ni boleh menimbulkan chaos, tapi
setakat ni ok je.

2) Magic lagi, kali ni katakan kita ada class terima generic (type
parameter), kita boleh config menggunakan generic shj tanpa class
sebenar. Contoh kita ada satu generic DataManager yg boleh terima
domain classes sbg type parameter T:

public class DataManager<T>() : IDataManager<T> { }

Kita boleh letak mcm ni shj:

<component id="DataManager">
service="AltNetMy.IDataManager`1, AltNetMy"
type="AltNetMy.DataManager`1, AltNetMy"
</component>

dan panggil mcm ni:

var dm = Container.Resolve<IDataManager<Profile>>();

Kita tak perlu setup satu2 data manager utk domain classes mcm ni:

<component id="DataManager">
service="AltNetMy.IDataManager`1[[AltNetMy.Profile,
AltNetMy]], AltNetMy"
type="AltNetMy.DataManager`1[[AltNetMy.Profile, AltNetMy]], AltNetMy"
</component>

Walaupun boleh jugak nak buat, tapi kita boleh skip.

3) Boleh panggil menggunakan interface (attribute "service" dlm
config), instead of id

var repo = Container.Resolve<IProfileRepository>();

berbanding cara Spring

var repo = (IProfileRepository)ctx.GetObject("ProfileRepository");

Tapi kita boleh jugak buat mcm Spring punya jugak. Dan kita boleh
letak banyak implementation utk satu interface, tapi bila panggil dgn
specify interface shj tanpa id, Windsor akan ambil yg mana paling awal
dlm config.

4) Dah disebut tadi, Windsor tak membezakan constructor atau property
injection, tapi Spring ya. Maybe boleh celaru kot, tapi aku suka mcm
ni.

Tak tau la kalau skrg Spring dah byk ubah. Masa aku cuba dulu support
utk generic pun tak berapa bagus lagi.

ryzam

unread,
Jan 9, 2009, 4:15:08 AM1/9/09
to ALTNETMY
InsyAllah kalau ada masa, aku try tunjukkan Autofac DI

ron

unread,
Jan 10, 2009, 10:48:49 AM1/10/09
to altn...@googlegroups.com
Good explanation, thanks :)

2009/1/8 Ikhwan Hayat <ikhwa...@gmail.com>
Reply all
Reply to author
Forward
0 new messages