Design Pattern, OO Design Principles

The Open Closed Principle

Open / Closed Principle:

Đây là nguyên lý thứ 2 trong SOLID (5 nguyên lý cơ bản của thiết kế hướng đối tượng). Nguyên lý này đề cập đến việc một đối tượng phải luôn MỞ cho việc mở rộng, nhưng ĐÓNG trong việc thay đổi. Nghĩa là bạn phải làm thế nào để tránh được những thay đổi trong lớp khi có những sự thay đổi ở bên ngoài và vẫn phải đảm bảo tính linh hoạt để đáp ứng sự thay đổi đó. Một cách đơn giản để đạt được điều đó là: Hãy tách những thành phần dễ thay đổi ra khỏi những phần khó thay đổi. Mỗi khi có thay đổi, chúng ta chỉ làm việc trên những phần cần thay đổi mà vẫn đảm bảo không ảnh hưởng đến những phần còn lại.

Hãy cùng xem xét 1 ví dụ sau: Yêu cầu là chúng ta hãy viết 1 lớp để đảm nhận việc kết nối tới các hệ quản trị cơ sở dữ liệu khác nhau như: SQL Server hoặc MySQL.

Thiết kế ban đầu như sau:

SqlServerConnection.java

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

/**
 * SqlServerConnection
 * @author Edward
 */
public class SqlServerConnection {

	/**
	 * Connection process
	 * @author Edward
	 */
	public void doConnect() {
		System.out.println("Connect to SQL Server");
	}

}

MySqlConnection.java

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

/**
 * MySqlConnection
 * @author Edward
 */
public class MySqlConnection {

	/**
	 * Connection process
	 * @author Edward
	 */
	public void doConnect() {
		System.out.println("Connect to MySQL Server");
	}
}

ConnectionManager.java

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

/**
 * ConnectionManager will help us connect to many kind of servers we want
 * @author Edward
 */
public class ConnectionManager {

	/**
	 * Do connect to SQL Server
	 * @author Edward
	 * @param inConnection
	 */
	void doSqlServerConnect(SqlServerConnection inConnection) {
		// Let manager class do something before Server is started
		// ...

		// Connect to server
		inConnection.doConnect();

		// The manager will do something more after Server is stopped
		// ...
	}

	/**
	 * Do connect to MySQL Server
	 * @author Edward
	 * @param inConnection
	 */
	void doMySqlConnect(MySqlConnection inConnection) {
		// Let manager class do something before Server is started
		// ...

		// Connect to server
		inConnection.doConnect();

		// The manager will do something more after Server is stopped
		// ...
	}
}

Main.java

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

/**
 * Class desciption
 * @author Edward
 */
public class Main {

	/**
	 * Main class
	 * @author Edward
	 * @param args
	 */
	public static void main(String[] args) {
		ConnectionManager connectionPool = new ConnectionManager();

		connectionPool.doMySqlConnect(new MySqlConnection());
		connectionPool.doSqlServerConnect(new SqlServerConnection());
	}

}

Chạy hàm Main ta được:

Connect to MySQL Server
Connect to SQL Server

Kết quả vẫn đúng, và bây giờ là thời điểm của sự thay đổi. Bạn muốn connect tới Oracle server??? Đơn giản là tạo ra lớp OracleConnection, sau đó vào lớp ConnectionManager và viết thêm hàm doOracleConnect. Vậy còn PostgreSQL???
Bạn phải thay đổi quá nhiều (thật ra là chỉ tạo thêm 1 class và 1 phương thức, nhưng vẫn là quá nhiều).

Hãy thay đổi 1 chút trong lớp ConnectionManager:

ConnectionManager.java

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

/**
 * ConnectionManager will help us connect to many kind of servers we want
 * @author Edward
 */
public class ConnectionManager {

	/**
	 * Do connect to Oracle Server
	 * @author Edward
	 * @param inConnection
	 */
	void doConnect(Object inConnection) {
		// Let manager class do something before Server is started
		// ...

		// Connect to server
		if(inConnection instanceof SqlServerConnection) {
			((SqlServerConnection)inConnection).doConnect();
		}
		else if(inConnection instanceof OracleConnection) {
			((OracleConnection)inConnection).doConnect();
		}
		else if(inConnection instanceof MySqlConnection) {
			((MySqlConnection)inConnection).doConnect();
		}

		// The manager will do something more after Server is stopped
		// ...
	}
}

Main.java

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

/**
 * Class desciption
 * @author Edward
 */
public class Main {

	/**
	 * Main class
	 * @author Edward
	 * @param args
	 */
	public static void main(String[] args) {
		ConnectionManager connectionPool = new ConnectionManager();

		connectionPool.doConnect(new MySqlConnection());
		connectionPool.doConnect(new OracleConnection());
		connectionPool.doConnect(new SqlServerConnection());
	}

}

Chạy hàm Main ta được:

Connect to MySQL Server
Connect to Oracle Server
Connect to SQL Server

Bây giờ chúng chỉ còn hàm doConnect trong lớp ConnectionManager (lớp Main cũng khác chút ít). Nhưng vẫn không đảm bảo được việc không phải thay đổi khi có 1 Connection mới được thêm vào.

Quay lại với chủ đề ngày hôm nay, nguyên lý Đóng / Mở (thuần Việt). Hãy nhớ lại về mục tiêu của Các nguyên lý thiết kế hướng đối tượng ở bài trước: … (xem ở đây). Bằng cách áp dụng 1 chút abstract vào, mọi việc sẽ trở nên đơn giản rất nhiều.

Hãy cùng xem xét:
1. 3 lớp MySqlConnection, OracleConnection, SqlServerConnection đều có chung một việc là kết nối tới Server tương ứng, và chúng ta có thể gọi chúng là Connection
2. Hàm doConnect trong lớp ConnectionManager (là hàm rất dễ bị thay đổi khi có một Connection được thêm vào) giúp các Connection connect đến đúng Server của chúng.
3. Bằng cách trừu tượng hóa (abstractionalization) các đối tượng Connection, chúng ta có thể giúp hàm doConnect có thể đối xử với các Connection với chung 1 phương thức.
(Liên hệ 1 tí về việc khi chúng ta nhìn ra đường và nói “Rất nhiều xe đang chạy kìa”, chúng ta không ám chỉ đến xe cụ thể là gì, có thể là xe máy, oto, nhưng chúng ta bik, là các xe vẫn đang chạy theo cách của chúng, xe máy 2 bánh, oto 4 bánh chẳng hạn)

1. Chúng ta cần có 1 lớp cơ sở (BASE) gọi là Connection, các lớp Connection cụ thể (MySqlConnection, OracleConnection, SqlServerConnection) sẽ kế thừa từ Connection.
2. Hàm doConnect sẽ làm việc với Connection.
3. Bằng cách trừu tượng hóa phương thức connect, chúng ta sẽ làm cho lớp ConnectionManager đối xử với các lớp cụ thể qua lớp BASE của nó.

Phiên bản mới của chương trình:

Connection.java

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

/**
 * Connection base class
 * @author Edward
 */
public abstract class Connection {
	public abstract void doConnect();
}

SqlServerConnection.java

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

/**
 * SqlServerConnection
 * @author Edward
 */
public class SqlServerConnection extends Connection {

	/**
	 * Connection process
	 * @author Edward
	 */
	public void doConnect() {
		System.out.println("Connect to SQL Server");
	}
}

OracleConnection.java

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

/**
 * OracleConnection
 * @author Edward
 */
public class OracleConnection extends Connection {

	/**
	 * Connection process
	 * @author Edward
	 */
	public void doConnect() {
		System.out.println("Connect to Oracle Server");
	}
}

MySqlConnection.java

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

/**
 * MySqlConnection
 * @author Edward
 */
public class MySqlConnection extends Connection {

	/**
	 * Connection process
	 * @author Edward
	 */
	public void doConnect() {
		System.out.println("Connect to MySQL Server");
	}
}

ConnectionManager.java

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

/**
 * ConnectionManager will help us connect to many kind of servers we want
 * @author Edward
 */
public class ConnectionManager {

	/**
	 * Do connect to Oracle Server
	 * @author Edward
	 * @param inConnection
	 */
	void doConnect(Connection inConnection) {
		// Let manager class do something before Server is started
		// ...

		// Connect to server
		inConnection.doConnect();

		// The manager will do something more after Server is stopped
		// ...
	}
}

Main.java

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

/**
 * Class desciption
 * @author Edward
 */
public class Main {

	/**
	 * Main class
	 * @author Edward
	 * @param args
	 */
	public static void main(String[] args) {
		ConnectionManager connectionPool = new ConnectionManager();

		connectionPool.doConnect(new MySqlConnection());
		connectionPool.doConnect(new OracleConnection());
		connectionPool.doConnect(new SqlServerConnection());
	}

}

Kết quả sẽ ra:

Connect to MySQL Server
Connect to Oracle Server
Connect to SQL Server

Lớp Connection là lớp abstract (vì nó sẽ không thực hiện 1 việc connect cụ thể đến Server nào), hàm doConnect của nó cũng là abstract và các lớp con MySqlConnection, OracleConnection, SqlServerConnection sẽ thực hiện override hàm doConnect để thực hiện việc connect đến Server tương ứng.
Lớp ConnectionManager bây giờ chỉ là việc với lớp abstract Connection
Và khi chúng ta muốn kết nối tới PostgreSQL, chỉ cần tạo ra lớp PostgreSQL kế thừa từ Connection và override hàm doConnect để thực hiện việc connect tới PostgreSQL Server

Kết thúc. Bài viết của mình có vẻ khá đơn giản, nhưng hi vọng là cũng đủ để cho các bạn mới có một cách nhìn mới về việc thiết kế các lớp trong chương trình, tận dụng các ưu điểm của OOP để làm cho chương trình bền vững và khó bị thay đổi quá nhiều, khi có yêu cầu thay đổi, cố gắng mở rộng chứ không thay đổi. Điều cuối cùng, hãy nhớ kỹ về tính ĐÓNG MỞ của chương trình, và 1 câu bonus: “programming to interface not implementation”. Chúng ta sẽ bàn về abstract, interface (không phải về sự giống, khác nhau mà ở ngữ cảnh sử dụng thực tế, khi nào) ở một bài viết khác.

Happy learning!!!

Nguồn: https://edwardthienhoang.wordpress.com/
Email: edwardthienhoang@gmail.com
Advertisements

6 thoughts on “The Open Closed Principle”

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