Design Pattern, Java, OO Design Principles

The Dependency Inversion Principle

The Dependency Inversion Principle (DIP)

(Tạm dịch là nguyên lý đảo ngược sự phụ thuộc)

Đây là nguyên lý cuối cùng trong 5 nguyên lý cơ bản trong thiết kế hướng đối tượng SOLID. Nguyên lý này chỉ ra rằng các lớp high-level không được phụ thuộc vào các lớp low-level. Thay vì để các lớp high-level sẽ sử dụng các interface do các lớp low-level định nghĩa và thực thi, thì nguyên lý này chỉ ra rằng các lớp high-level sẽ định nghĩa ra các interface, sau đó các lớp low-level sẽ thực thi các interface đó. (ĐẢO NGƯỢC ở chỗ đó)

Hãy xem ví dụ dưới đây:
Trường hợp 1:

Design ko áp dụng nguyên lý DIP: các lớp cấp cao phải phụ thuộc vào các interface của lớp cấp thấp
DIP2
Với kiểu design như vậy, thì các lớp cấp cao phải biết tất cả các interface của các lớp con. Khi có thêm một lớp cấp thấp thêm vào, chúng ta phải tiến hành thay đổi trong lớp cấp cao để nó có thể hiểu được các interface của lớp mới thêm vào. Điều này gây ra rất nhiều khó khăn trong việc chỉnh sửa. bảo trì và kiểm thử, nó vi phạm nguyên lý Open Close Principle.

Ví dụ cụ thể: dưới đây là mô hình của chương trình Copy program mà không sử dụng DIP:
DIP4
Tạm thời bỏ qua phần màu cam đứt quãng. Có thể thấy chương trình trên nhận các input từ bàn phím, và sẽ ghi ra 1 file text. Chương trình hiện tại có thể làm việc OK. Sau đó 1 khoảng thời gian, có 1 yêu cầu mới yêu cầu bạn support chương trình Copy này sao cho nó có thể ghi ra database, vậy là bạn phải thay đổi code trong Copy program để nó có thể làm việc ghi ra database. Điều này gây ra rất nhiều khó khăn trong việc chỉnh sửa. bảo trì và kiểm thử, nó vi phạm nguyên lý Open Close Principle. (Mình vừa lặp lại câu trên, điều này cũng vi phạm 1 nguyên lý trong thiết kế hướng đối tượng, và mình sẽ bàn trong dịp khác)

Trường hợp 2:

Design áp dụng nguyên lý DIP: thay vì để các lớp cấp cao phải phụ thuộc vào các lớp cấp thấp, ta sẽ đặt các interface ở lớp cấp cao, và buộc các lớp con phải định nghĩa lại.
DIP3
Như vậy các lớp ở level cao sẽ không còn phải phụ thuộc vào các lớp thấp nữa, nó chỉ cần biết và thực thi duy nhất interface mà nó đã tạo ra. Mỗi khi thêm mới 1 lớp cấp thấp vào, lớp đó phải tuân theo và thực thi interface mà lớp cấp cao đã định nghĩa.

Ví dụ cụ thể: dưới đây là mô hình của chương trình Copy program áp dụng DIP:
DIP5
Như ta đã thấy, bây giờ Copy program chỉ còn phụ thuộc vào 2 interface duy nhất: Reader và Writer do chính nó định nghĩa. Ở dưới là các lớp low-level, chúng ta có thể có nhiều lớp để thực hiện việc đọc, ghi, và điểm chung giữa chúng là sẽ phải thực thi một trong 2 interface mà Copy program đã định nghĩa. Khi có 1 lớp đọc hoặc ghi được thêm vào, muốn cho lớp trên nó hiểu, điều đơn giản chúng ta làm là để cho lớp đó thực thi interface mà lớp trên nó đã đưa ra, như vậy sẽ đảm bảo việc chúng ta sẽ không phải thay đổi gì cả ở lớp trên.

Dưới đây là chương trình được viết bằng Java để minh họa cho nguyên lý đảo ngược phụ thuộc (DIP)

IReader.java

/**
 *    	OO Design Principle Tutorial
 *		View more at: https://edwardthienhoang.wordpress.com/
 */
package edward.tutorial.designprinciple.dependencyinversion;

/**
 * IReader class
 * This class will work with high-level class (Copy class)
 * @author Edward
 */
public interface IReader {

	/**
	 * High-level class (Copy class) will only work with this class
	 * All low-level class must implement this method
	 * so that high-level class can know it
	 *
	 * @author Edward
	 * @param inInput
	 */
	public String read();
}

IWriter.java

/**
 *    	OO Design Principle Tutorial
 *		View more at: https://edwardthienhoang.wordpress.com/
 */
package edward.tutorial.designprinciple.dependencyinversion;

/**
 * IWriter
 * This class will work with high-level class (Copy class)
 * @author Edward
 */
public interface IWriter {

	/**
	 * High-level class (Copy class) will only work with this class
	 * All low-level class must implement this method
	 * so that high-level class can know it
	 *
	 * @author Edward
	 * @param inInput
	 */
	public void write(String inInput);
}

Copy.java

/**
 *    	OO Design Principle Tutorial
 *		View more at: https://edwardthienhoang.wordpress.com/
 */
package edward.tutorial.designprinciple.dependencyinversion;

/**
 * Copy class
 * I assume it is high-level class in this example
 * It only know 2 interfaces: IReader and IWriter
 * @author Edward
 */
public class Copy {

	/** mReader */
	private IReader mReader;

	/** mWriter */
	private IWriter mWriter;

	/**
	 * Constructor
	 * @param inReader
	 * @param inWriter
	 */
	public Copy(IReader inReader, IWriter inWriter) {
		if(inReader == null || inWriter == null) {
			throw new NullPointerException();
		}
		mReader = inReader;
		mWriter = inWriter;
	}

	/**
	 * It will take an input by using a Reader
	 * then write to an ouptut by using a Writer
	 * @author Edward
	 */
	public void doWork() {
		mWriter.write(mReader.read());
	}
}

Keyboard.java

/**
 *    	OO Design Principle Tutorial
 *		View more at: https://edwardthienhoang.wordpress.com/
 */
package edward.tutorial.designprinciple.dependencyinversion;

/**
 * Keyboard class
 * It's concrete class of IReader
 * @author Edward
 */
public class Keyboard implements IReader {

	@Override
	public String read() {
		java.util.Scanner in = new java.util.Scanner(System.in);
		System.out.print("Input a string: ");
	    return in.nextLine();
	}

}

Scanner.java

/**
 *    	OO Design Principle Tutorial
 *		View more at: https://edwardthienhoang.wordpress.com/
 */
package edward.tutorial.designprinciple.dependencyinversion;

/**
 * Scanner class
 * This class will work with high-level class (Copy class)
 * @author Edward
 */
public class Scanner implements IReader {

	@Override
	public String read() {
		return "Sample of scanning data";
	}

}

Printer.java

/**
 *    	OO Design Principle Tutorial
 *		View more at: https://edwardthienhoang.wordpress.com/
 */
package edward.tutorial.designprinciple.dependencyinversion;

/**
 * Printer class
 * It's concrete class of IWriter
 * @author Edward
 */
public class Printer implements IWriter {

	@Override
	public void write(String inInput) {
		System.out.println("The text will be printed to paper: " + inInput);
	}

}

Database.java

/**
 *    	OO Design Principle Tutorial
 *		View more at: https://edwardthienhoang.wordpress.com/
 */
package edward.tutorial.designprinciple.dependencyinversion;

/**
 * Database class
 * It's concrete class of IWriter
 * @author Edward
 */
public class Database implements IWriter{

	@Override
	public void write(String inInput) {
		System.out.println("The data will be updated to database: " + inInput);
	}

}

Main.java

/**
 *    	OO Design Principle Tutorial
 *		View more at: https://edwardthienhoang.wordpress.com/
 */
package edward.tutorial.designprinciple.dependencyinversion;

/**
 * Main class.
 * It's used for testing
 * @author Edward
 */
public class Main {

	/**
	 * Main method
	 * @author Edward
	 * @param args
	 */
	public static void main(String[] args) {
		// In this example, we will read a string from user's input
		// then write to database
		// You can try with other kinds of Reader and Writer you have
		IReader reader = new Keyboard();
		IWriter writer = new Database();

		Copy copy = new Copy(reader, writer);

		copy.doWork();
	}

}

Lời kết

Vậy là cũng kết thúc nguyên lý cuối cùng trong 5 nguyên lý cơ bản của thiết kế hướng đối tượng SOLID. SOLID được ví như là “nhập môn tâm pháp” nếu như các bạn muốn phát triển ứng dụng với các ngôn ngữ hướng đối tượng. Chỉ cần chúng ta nắm vững các nguyên lý trong việc thiết kế cấu trúc chương trình thì sẽ giúp cho chương trình chúng ta… (đọc lại ở đây nhé).
Tuy nhiên, các nguyên lý này chỉ mới chỉ ra cho chúng ta biết, thiết kế nào là đúng, thiết kế nào là sai chứ chưa giúp chúng ta giải quyết được vấn đề. Trong khi làm thực tế, gặp phải những vấn đề cụ thể như: làm thế nào để giảm thiểu số lượng các đối tượng phải tạo ra trong chương trình, làm thế nào để tích hợp một module có sẵn vào trong hệ thống của mình… Tất cả những chuyện như vậy sẽ được giải quyết bằng cách áp dụng các “Mẫu thiết kế” (Design Pattern). Nói đơn giản hơn, các mẫu thiết kế sẽ giúp chúng ta giải quyết những bài toán thường gặp trong những ngữ cảnh nhất định. Các mẫu thiết kế cũng tuân theo các nguyên lý thiết kế hướng đối tượng làm cơ sở. Vì vậy việc nắm vững các nguyên lý này là điều cần thiết nếu các bạn muốn tiến sâu hơn trong việc tạo ra 1 sản phẩm có kiến trúc đẹp, chất lượng. Mình sẽ tiếp tục viết loạt bài về Mẫu thiết kế (Trung cấp tâm pháp) trong thời gian tới. Còn trong thời gian này, trong khi chờ đợi các bạn ngấm từ từ các nguyên lý cơ bản, mình sẽ làm 1 số bài viết về các nguyên lý nâng cao hơn để mọi người sẽ có cái nhìn rõ ràng hơn về những cái căn bản nhất của thế giới hướng đối tượng.

NGUỒN: HTTP://EDWARDTHIENHOANG.WORDPRESS.COM/
EMAIL: EDWARDTHIENHOANG@GMAIL.COM
Advertisements

6 thoughts on “The Dependency Inversion Principle”

  1. Rất hay. Tiếp đi bạn, bạn đã làm cho mình thấy sức mạnh của sự thiết kế . Đo´ giờ cư´ code không theo chuẩn giˋ cả . Cảm ơn bạn nha

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s