Avoid NullPointerException in Java

I would like to share with you some tips to deal/prevent NPE in your code:

The BIG IMPORTANT rule: do NOT assign / pass / return null value and keep your code as cohesive as possible.

Programming tips / techniques:
1. Return an empty collections instead of null.
For example:
List:

return Collections.emptyList()

Set:

return Collections.emptySet()

Map:

return Collections.emptyMap()
org.apache.commons.lang.ArrayUtils.EMPTY_XXX_ARRAY

2. Return

org.apache.commons.lang.StringUtils.EMPTY

or “” instead of null

3. Return “UNKNOWN” enum instead of null

4. Check null before executing a variable

5. Null-safe checking technique

if("Hello".equals(hello)) { }

instead of

if(hello != null) {
	if(hello.equals("Hello")) { }
}

or

if(hello.equals("Hello")) { }

Principle and Pattern:
6. Null Object Pattern (https://sourcemaking.com/design_patterns/null_object/java-0)
(My favourite pattern belong with Template Method and Bridge)
Instead of

public Manager getManager(ManagerType inType){
	switch(inType) {
		case PROJECT:
			return ProjectManager();
		case DEPARTMENT:
			return DepartmentManager();
		default:
			break;
	}
	return null;
}

Let try:

public Manager getManager(ManagerType inType){
	switch(inType) {
		case PROJECT:
			return ProjectManager();
		case DEPARTMENT:
			return DepartmentManager();
		default:
		break;
	}
	return NullManager();
}

Design tips:

class NullManager extends Manager {
	// Implement all abstract methods of Manager
	// Override neccessary methods to drive to do "Nothing" or "Default" action.
}

7. Avoid too many dot syntax. (http://en.wikipedia.org/wiki/Law_of_Demeter)
For example:

getSettingManager().getSettingPanel().getHeaderPanel().getHeaderLabel().setText("Setting");

Class Variable:
8. Try initializing variable member of class before using it.
Direct initialization

private List mPersons = new ArrayList();

Initialize them in class Constructor

public Family() {
	mPersons = new ArrayList();
}

Constructor dependency injection

public Family(List inPersons) {
	mPersons = inPersons;
}

Provide accessibility through getter and using lazy initialization

public getPersons() {
	if(mPersons == null) {
	mPersons = new ArrayList();
	}
	return mPersons;
}

9. Avoid / eliminate method dependency injection (setter)

public setPersons(List inPersons) {
	mPersons = inPersons;
}
NGUỒN: HTTP://EDWARDTHIENHOANG.WORDPRESS.COM/
EMAIL: EDWARDTHIENHOANG@GMAIL.COM

[/sourcecode]

Write Private Functions Not Private Methods

From http://pivotallabs.com/write-private-functions-not-private-methods/

Khi một class có quá nhiều private method (được gọi là “Lớp tảng băng trôi” – Iceberg class) thì đó là dấu hiệu cho biết class đó có thể đang làm hơn 1 nhiệm vụ (responsibility). Vì vậy việc tách (extract) những private method đó ra class khác giúp lớp sẽ tuân thủ theo nguyên tắc Single Responsibility Principle. Qua đó, việc loại bỏ các private method còn giúp ích cho các lập trình viên trong việc viết Unit Test.

Cùng xem bài viết của Jared Carroll để rõ thêm về điều này.

During refactoring, private methods are created in order to:

  • Eliminate duplication within the class
  • Clarify confusing and/or complex fragments of related code (Extract Method)

These are both great refactorings, but be cautious of classes with an excessive amount of private class or instance methods. This is a smell that often indicates a class is doing too much work. Help maintain cohesion by refactoring private methods into separate objects. In this post, we’ll look at a way of writing private methods that encourages future extraction.

ICEBERG CLASSES AND HIDDEN ABSTRACTIONS

An iceberg class is a class that has more private methods (3? 5? 10?) than public methods. Private methods are often an indication of additional, hidden responsibilities. By adopting a functional style, private methods can be easily extracted into separate objects.

A private function is a private method that:

  • Calculates its result only from its arguments
  • Does not rely on any instance (or global) state

AN EXAMPLE – FROM METHODS, TO FUNCTIONS, TO EXTRACTION

Below is a modified portion of the User model from a Ruby on Rails Tutorial sample app.

class User < ActiveRecord::Base
  attr_accessor   :password
  attr_accessible :name, :email, :password, :password_confirmation

  before_create :encrypt_password

  # public methods...

  private

  def encrypt_password
    self.salt = make_salt
    self.encrypted_password = encrypt(password)
  end

  def make_salt
    secure_hash("#{Time.now.utc}--#{password}")
  end

  def encrypt(string)
    secure_hash("#{salt}--#{string}")
  end

  def secure_hash(string)
    Digest::SHA2.hexdigest(string)
  end
end

When a User is created their salt and encrypted password are set. Four private methods are used to implement this.User#encrypt_password is called by ActiveRecord, which means we have no control over sending arguments to it, so it will have to remain a method. Of the remaining three private methods, only one User#secure_hash, is a function.

Let’s refactor the other two into functions.

class User < ActiveRecord::Base
  # same implementation as above...

  private

  def encrypt_password
    self.salt = make_salt(password)
    self.encrypted_password = encrypt(salt, password)
  end

  def make_salt(password)
    secure_hash("#{Time.now.utc}--#{password}")
  end

  def encrypt(salt, password)
    secure_hash("#{salt}--#{password}")
  end

  def secure_hash(string)
    Digest::SHA2.hexdigest(string)
  end
end

Encryption doesn’t feel like a User responsibility, so let’s extract it into a separate object.

class Encryptor
  def self.make_salt(password)
    secure_hash("#{Time.now.utc}--#{password}")
  end

  def self.encrypt(salt, password)
    secure_hash("#{salt}--#{password}")
  end

  def self.secure_hash(string)
    Digest::SHA2.hexdigest(string)
  end

  private_class_method :secure_hash
end

We’re still left with one private class function. This seems ok because it’s related to Encryptor‘s core responsibility. Also, Encryptor.make_salt is not a function because it relies on global state, Time.now; this will make unit testing it difficult. Let’s punt on fixing that for now, because this class is already an improvement.

Finally let’s update User by having it collaborate with our new Encryptor class.

class User < ActiveRecord::Base
  # same implementation as above...

  private

  def encrypt_password
    self.salt = Encryptor.make_salt(password)
    self.encrypted_password = Encryptor.encrypt(salt, password)
  end
end

THE SINGLE RESPONSIBILITY PRINCIPLE AND OBJECT-ORIENTED CEREMONY

There are two disadvantages of extracting private functions into new classes:

  • Naming the new abstraction is difficult because it’s often a verb and not a noun
  • The message sender now has to now instantiate a class and send it a message

Our above User refactoring resulted in a somewhat awkward, doer class (Encryptor). I also only used class methods in Encryptor, essentially creating a namespace of functions. This eliminates the need to instantiate a separate class, but it doesn’t feel very object-oriented.

I don’t see a solution for either of these two disadvanages. They’re by-products of modeling software in an object-oriented way.

KEEPING CLASSES COHESIVE

Cohesive, single responsibility classes are easy to understand and reuse. Private methods are one indication that a class is beginning to take on additional responsibilities. By writing private methods in a functional style, you take the first step in preserving a class’s true responsibility.

From Primitive Obsession to Domain Modelling

From http://blog.ploeh.dk/2015/01/19/from-primitive-obsession-to-domain-modelling/

A string is sometimes not a string. Model it accordingly.

Recently, I was reviewing some code that looked like this:

public IHttpActionResult Get(string userName)
{
    if (string.IsNullOrWhiteSpace(userName))
        return this.BadRequest("Invalid user name.");
 
    var user = this.repository.FindUser(userName.ToUpper());
    return this.Ok(user);
}

There was a few things with this that struck me as a bit odd; most notably the use of IsNullOrWhiteSpace. When I review code, IsNullOrWhiteSpace is one of the many things I look for, because most people use it incorrectly.

This made me ask the author of the code why he had chosen to use IsNullOrWhiteSpace, or, more specifically, what was wrong with a string with white space in it?

The answer wasn’t what I expected, though. The answer was that it was a business rule that the user name can’t be all white space.

You can’t really argue about business logic.

In this case, the rule even seems quite reasonable, but I was just so ready to have a discussion about invariants (pre- and postconditions) that I didn’t see that one coming. It got me thinking, though.

Where should business rules go?

It seems like a reasonable business rule that a user name can’t consist entirely of white space, but is it reasonable to put that business rule in a Controller? Does that mean that everywhere you have a user name string, you mustremember to add the business rule to that code, in order to validate it? That sounds like the kind of duplication that actually hurts.

Shouldn’t business rules go in a proper Domain Model?

This is where many programmers would start to write extension methods for strings, but that’s just putting lipstick on a pig. What if you forget to call the appropriate extension method? What if a new developer on the team doesn’t know about the appropriate extension method to use?

The root problem here is Primitive Obsession. Just because you can represent a value as a string, it doesn’t mean that you always should.

The string data type literally can represent any text. A user name (in the example domain above) can not be any text – we’ve already established that.

Make it a type

Instead of string, you can (and should) make user name a type. That type can encapsulate all the business rules in a single place, without violating DRY. This is what is meant by a Domain Model. In Domain-Driven Design terminology, a primitive like a string or a number can be turned into a Value Object. Jimmy Bogard already covered that ground years ago, but here’s how I would define a UserName class:

public class UserName
{
    private readonly string value;
 
    public UserName(string value)
    {
        if (value == null)
            throw new ArgumentNullException("value");
        if (!UserName.IsValid(value))
            throw new ArgumentException("Invalid value.", "value");
 
        this.value = value;
    }
 
    public static bool IsValid(string candidate)
    {
        if (string.IsNullOrEmpty(candidate))
            return false;
 
        return candidate.Trim().ToUpper() == candidate;
    }
 
    public static bool TryParse(string candidate, out UserName userName)
    {
        userName = null;
        if (string.IsNullOrWhiteSpace(candidate))
            return false;
 
        userName = new UserName(candidate.Trim().ToUpper());
        return true;
    }
 
    public static implicit operator string(UserName userName)
    {
        return userName.value;
    }
 
    public override string ToString()
    {
        return this.value.ToString();
    }
 
    public override bool Equals(object obj)
    {
        var other = obj as UserName;
        if (other == null)
            return base.Equals(obj);
 
        return object.Equals(this.value, other.value);
    }
 
    public override int GetHashCode()
    {
        return this.value.GetHashCode();
    }
}

As you can tell, this class protects its invariants. In case you were wondering about the use of ToUpper, it turns out that there’s also another business rule that states that user names are case-insensitive, and one of the ways you can implement that is by converting the value to upper case letters. All business rules pertaining to user names are now nicely encapsulated in this single class, so you don’t need to remember where to apply them to strings.

If you want to know the underlying string, you can either invoke ToString, or take advantage of the implicit conversion from UserName to string. You can also compare to UserName instances, because the class overrides Equals. If you have a string, and want to convert it to a UserName, you can use TryParse.

The original code example above can be refactored to use the UserName class instead:

public IHttpActionResult Get(string candidate)
{
    UserName userName;
    if (!UserName.TryParse(candidate, out userName))
        return this.BadRequest("Invalid user name.");
 
    var user = this.repository.FindUser(userName);
    return this.Ok(user);
}

This code has the same complexity as the original example, but now it’s much clearer what’s going on. You don’t have to wonder about what looks like arbitrary rules; they’re all nicely encapsulated in the UserName class.

Furthermore, as soon as you’ve left the not Object-Oriented boundary of your system, you can express the rest of your code in terms of the Domain Model; in this case, the UserName class. Here’s the IUserRepository interface’s Find method:

User FindUser(UserName userName);

As you can tell, it’s expressed in terms of the Domain Model, so you can’t accidentally pass it a string. From that point, since you’re receiving a UserName instance, you know that it conforms to all business rules encapsulated in the UserName class.

Not only for OOP

While I’ve used the term encapsulation once or twice here, this way of thinking is in no way limited to Object-Oriented Programming. Scott Wlaschin describes how to wrap primitives in meaningful types in F#. The motivation and the advantages you gain are the same in Functional F# as what I’ve described here.

Over-engineering?

Isn’t this over-engineering? A 56 lines of code class instead of a string? Really? The answer to such a question is always context-dependent, but I rarely find that it is (over-engineering). You can create a Value Object like the above UserName class in less than half an hour – even if you use Test-Driven Development. When I created this example, I wrote 27 test cases distributed over five test methods in order to make sure that I hadn’t done something stupid. It took me 15 minutes.

You may argue that 15 minutes is a lot, compared to the 0 minutes it would take you if you’d ‘just’ use a string. On the surface, that seems like a valid counter-argument, but perhaps you’re forgetting that with a primitive string, you still need to write validation and business logic ‘around’ the string, and you have to remember to apply that logic consistently across your entire code base. My guess is that you’ll spend more than 15 minutes on doing this, and troubleshooting defects that occur when someone forgets to apply one of those rules to a string in some other part of the code base.

Summary

Primitive values such as strings, integers, decimal numbers, etc. often represent concepts that are constrained in some ways; they’re not just any string, any integer, or any decimal number. Ask yourself if extreme values (like the entire APPP manuscript, Int32.MinValue, and so on) are suitable for such variables. If that’s not the case, consider introducing a Value Object instead.

If you want to learn more about Encapsulation, you can watch Encapsulation and SOLID Pluralsight course.

Factory Pattern – Nhà máy của những đối tượng

Factory Pattern

Chào mọi người, hôm nay mình sẽ tiếp tục loạt bài về Design Pattern với một pattern mới: Factory Pattern. Pattern này sẽ giúp chúng ta có một hướng giải quyết đúng đắn khi đối mặt với việc phải dùng những lệnh if/else trong khối xử lý để tạo ra những đối tượng khác nhau dựa vào điều kiện ban đầu cho trước.

Khi không có nhà máy

Chúng ta bắt đầu câu chuyện tại một công ty IT có tên AZ Company cùng với kiến trúc sư có tuổi nhưng chưa có tên A11. Lúc này A11 đang đảm nhận dự án xây dựng 1 hệ thống có tên là “Course Management System” (CMS) để quản lý các khóa học trực tuyến. Cùng xem A11 sẽ làm thế nào để xây dựng hệ thống này, anh ta am hiểu khá rõ về các khái niệm trong lập trình hướng đối tượng như kế thừa và đa hình, vậy là thiết kế ban đầu ra đời ngay sau đó mà không mất quá nhiều suy nghĩ.
Factory_1

public class CourseController {
	public static String COURSE_JAVA = "Java";
	public static String COURSE_DOT_NET = ".NET";
	public void displayCourse(String inCourseName) {
		AbstractCourse course = null;
		if(inCourseName.equals(COURSE_JAVA)) {
			course = new JavaCourse();
		}
		else if(inCourseName.equals(COURSE_DOT_NET)) {
			course = new DotNetCourse();
		}
		if(course != null) {
			course.display();
		}
		else {
			System.out.println("Nothing is displayed");
		}
	}
}

public class FactoryTest {
	public static void main(String[] args) {
		CourseController controller = new CourseController();
		controller.displayCourse(CourseController.COURSE_DOT_NET);
	}
}

Hệ thống của A11 được đưa vào vận hành và không gặp bất kỳ vấn đề gì và mang lại lợi nhuận khá lớn cho công ty từ những khóa học online. Lúc này sếp của A11 muốn mở thêm một khóa học C/C++. A11 bắt đầu suy nghĩ và anh ta định sẽ vào lớp Controller để thêm 1 dòng if dành cho COURSE_C_PLUS_PLUS. Nhưng ý định đó dừng lại khi anh nghĩ đến việc sếp sẽ mở thêm hàng chục khóa học online khác, và chi phí để anh thay đổi cả lớp CourseController để đáp ứng lại là quá lớn. A11 nghĩ đến nguyên lý SOLID principle OCP (Open Closed Principle) và thấy mình đã vi phạm nó. Một hệ thống luôn phải được thay đổi theo thời gian và phải có cách nào đó để có thể đáp ứng được những thay đổi mà không phải sửa code (thật ra là sửa 1 chút, nhưng sửa đúng chỗ cần sửa). Bạn không thể nào giải phẫu nguyên một chiếc xe chỉ để vá cái bánh xe.

Bắt đầu xây dựng một nhà máy đơn giản

Câu chuyện sẽ tiếp tục, do đã học được từ những nguyên lý về thiết kế hướng đối tượng, ngày hôm sau, A11 đã quyết định sẽ tách biệt những phần thay đổi ra khỏi những phần ít bị thay đổi. Cụ thể ở đây là những đoạn if/else để khởi tạo những khóa học sẽ được thay thế bằng 1 “nhà máy” để khởi tạo những đối tượng đó một cách độc lập từ bên ngoài, kết quả của việc khởi tạo sẽ được đưa vào để xử lý cho những đoạn code phía sau.

Nhà máy đơn giản của A11:
Factory_2

public class CourseFactory {
	public static AbstractCourse createCourse(String inCourseName) {
		AbstractCourse course = null;
		if(inCourseName.equals(CourseController.COURSE_JAVA)) {
			course = new JavaCourse();
		}
		else if(inCourseName.equals(CourseController.COURSE_DOT_NET)) {
			course = new DotNetCourse();
		}
		return course;
	}
}

public class CourseController {
	public static String COURSE_JAVA = "Java";
	public static String COURSE_DOT_NET = ".NET";
	public void displayCourse(String inCourseName) {
		AbstractCourse course = CourseFactory.createCourse(inCourseName);
		if(course != null) {
			course.display();
		}
		else {
			System.out.println("Nothing is displayed");
		}
	}
}

Như đã thấy, phần mong manh dễ thay đổi nhất đã bị đưa ra một chỗ khác. A11 có thể tạm quên đi lớp CourseController mỗi khi có yêu cầu thêm mới 1 khóa học mới. Anh ta có thể ngủ ngon cho đến hết đêm nay và đêm mai nữa, ít ra là như vậy.

Nhà máy bị quá tải

Bây giờ công ty AZ đã thực sự lớn mạnh, bằng việc cung cấp các khóa học online, ngày càng có nhiều học viên đăng ký. Và trong số đó, có một số người đã không hài lòng với các khóa học online kiểu như vậy, họ muốn được học những khóa học offline, khi đó những câu hỏi sẽ được giải đáp nhanh hơn và hiệu quả hơn bằng cách tương tác trực tiếp với các giảng viên. Giám đốc công ty thấy được điều đó, đã mời thêm 1 số giáo sư về và bắt đầu cho học viên đăng ký những khóa học offline. Và tất nhiên A11 cũng có việc làm. Anh phải tiếp tục xây dựng những khóa học offline như vậy để thêm vào hệ thống.

Bây giờ hệ thống của A11 sẽ phải quản lý các khóa học như JavaOnlineCourse, DotNetOnlineCourse, CPlusPlusOnlineCourse, PHPOnlineCourse, JavaOfflineCourse, DotNetOfflineCourse, …

A11 sẽ thêm những khóa học Offline vào như hệ thống như thế nào? Anh suy nghĩ và câu trả lời chính là lớp CourseFactory. Anh sẽ thêm các điều kiện if/else vào để tạo ra những khóa học offline giống như việc anh làm với các khóa học online.

public class CourseFactory {
	public static AbstractCourse createCourse(String inCourseName) {
		AbstractCourse course = null;
		if(inCourseName.equals(CourseController.COURSE_JAVA_ONLINE)) {
			course = new JavaOnlineCourse();
		}
		else if(inCourseName.equals(CourseController.COURSE_DOT_NET_ONLINE)) {
			course = new DotNetOnlineCourse();
		}
		else if(inCourseName.equals(CourseController.COURSE_JAVA_OFFLINE)) {
			course = new JavaOfflineCourse();
		}
		else if(inCourseName.equals(CourseController.COURSE_DOT_NET_OFFLINE)) {
			course = new DotNetOfflineCourse();
		}
		return course;
	}
}

Hệ thống vẫn vận hành tốt. Mỗi khi có một khóa học online được mở ra, A11 sẽ vào CourseFactory để sửa, mỗi khi có một khóa học Offline mở ra, A11 cũng sẽ vào CourseFactory để sửa. Và mỗi khi có một loại hình khóa học mới được mở ra, lớp CourseFactory cũng sẽ được để ý đến… Phải chẳng CourseFactory bây giờ lại đang gặp vấn đề tương tự với CourseController ban đầu? Nó cũng bị ảnh hưởng nhiều mỗi khi có một yêu cầu mới (một loại hình khóa học mới). Nhắc lại nguyên lý SOLID Principle SRP – Single Responsibility Principle, mỗi lớp chỉ có một và duy nhất một lý do để thay đổi. Vậy thì hãy làm cho CourseFactory chỉ có một lý do duy nhất để thay đổi bằng cách tách rời những thành phần logic khác nhau trong một lớp ra những thành phần nhỏ hơn bằng những lớp khác. Lúc này A11 lại nghĩ ra một phương án mới đó là mỗi loại hình khóa học sẽ có một “nhà máy” cho riêng nó. Vì vậy trong thiết kế hiện tại sẽ có 2 nhà máy: OnlineFactory và OfflineFactory.

Xây dựng nhiều nhà máy

Thiết kế mới cho hệ thống multi-factory:
Factory_3

public class OfflineCourseFactory {
	public static AbstractCourse createCourse(String inCourseName) {
		AbstractCourse course = null;
		if(inCourseName.equals(CourseController.COURSE_JAVA_OFFLINE)) {
			course = new JavaOfflineCourse();
		}
		else if(inCourseName.equals(CourseController.COURSE_DOT_NET_OFFLINE)) {
			course = new DotNetOfflineCourse();
		}
		return course;
	}
}

public class OnlineCourseFactory {
	public static AbstractCourse createCourse(String inCourseName) {
		AbstractCourse course = null;
		if(inCourseName.equals(CourseController.COURSE_JAVA_ONLINE)) {
			course = new JavaOnlineCourse();
		}
		else if(inCourseName.equals(CourseController.COURSE_DOT_NET_ONLINE)) {
			course = new DotNetOnlineCourse();
		}
		return course;
	}
}

Chúng ta biết rõ, để đăng ký 1 khóa học online thì chỉ cần đoạn code sau:

AbstractCourse onlineCourse = OnlineCourseFactory.createCourse(CourseController.COURSE_JAVA_ONLINE);

Và đây là đoạn code để đăng ký 1 khóa học offline:

AbstractCourse offlineCourse = OfflineCourseFactory.createCourse(CourseController.COURSE_DOT_NET_OFFLINE);

Đưa nhiều nhà máy vào sử dụng

Vấn đề còn lại là tích hợp chúng vào lớp CourseController để tạo ra 1 khóa học theo yêu cầu và display thông tin lên màn hình??? Ngay bây giờ, mình không thể nghĩ ra cách nào để có thể có thể tạo ra 1 khóa học tại hàm displayCourse. Thứ nhất, tại đây, ConurseController không thể quyết định được là sẽ tạo ra 1 khóa học online hay 1 khóa học offline bởi vì OnlineCourseFactory và OfflineCourseFactory là 2 lớp tách biệt. Có thể là chúng ta cần một tham số nữa trong hàm displayCourse để quyết định xem nên tạo loại hình khóa học nào. Lúc đó displayCourse sẽ thành:

public void displayCourse(String inCourseType, String inCourseName) {
	AbstractCourse course = null;
	if(inCourseType.equals(COURSE_TYPE_ONLINE)) {
		course = OnlineCourseFactory.createCourse(inCourseName);
	}
	else if(inCourseType.equals(COURSE_TYPE_OFFLINE)) {
		course = OfflineCourseFactory.createCourse(inCourseName);
	}
	// else if(inCourseType.equals(...))
	if(course != null) {
		course.display();
	}
	else {
		System.out.println("Nothing is displayed");
	}
}

Bây giờ thì chúng ta lại quay về với bài toán if/else ở phía trên. Mỗi khi có một loại hình khóa học mới được thêm vào, chúng ta lại phải thêm 1 block if/else để tạo ra khóa học ứng với loại hình đó. Tại đây magic sẽ xuất hiện. Thử nghĩ xem nếu có một “nhà máy sản xuất nhà máy” thì thế nào nhỉ? Nghĩa là hãy thử trừu tượng hóa đối tượng Factory, do đó, lớp Controller sẽ không phải phụ thuộc vào các lớp nhà máy cụ thể như: OnlineCourseFactory và OfflineCourseFactory. Nếu các bạn đã đọc qua bài viết “Why extends is evil” hoặc các nguyên lý hướng đối tượng có thể hiểu được chỉ nên làm việc với các interface (abstract) chứ không phải là các lớp cụ thể. Vì vậy đừng ngần ngại mà abstract đối tượng nhà máy Factory.

Xây dựng nhà máy của những nhà máy

Thiết kế mới cho các đối tượng Factory sẽ như sau:
Factory_4

public abstract class AbstractCourseFactory {
	public AbstractCourse createCourse(String inCourseName) {
		AbstractCourse course = getCourse(inCourseName);
		if(course != null) {
			// Do a bunch of things here, such as initializing some information
			// for the course before returning it to caller
		}
		return course;
	}

	public abstract AbstractCourse getCourse(String inCourseName);
}
public class OfflineCourseFactory extends AbstractCourseFactory {
	public AbstractCourse getCourse(String inCourseName) {
		AbstractCourse course = null;
		if(inCourseName.equals(CourseController.COURSE_JAVA_OFFLINE)) {
			course = new JavaOfflineCourse();
		}
		else if(inCourseName.equals(CourseController.COURSE_DOT_NET_OFFLINE)) {
			course = new DotNetOfflineCourse();
		}
		return course;
	}
}
public class OnlineCourseFactory extends AbstractCourseFactory {
	public AbstractCourse getCourse(String inCourseName) {
		AbstractCourse course = null;
		if(inCourseName.equals(CourseController.COURSE_JAVA_ONLINE)) {
			course = new JavaOnlineCourse();
		}
		else if(inCourseName.equals(CourseController.COURSE_DOT_NET_ONLINE)) {
			course = new DotNetOnlineCourse();
		}
		return course;
	}
}

(Chú ý là mình đã thay đổi phương thức createCourse thành non-static nhé)

Quang cảnh cuối cùng

OK, hãy tái cấu trúc lại lớp CourseController để có một thiết kế hoàn chỉnh
Factory_5

Toàn bộ source chương trình sẽ như sau:

public class CourseConstants {
	public static String COURSE_JAVA_ONLINE = "JavaOnline";
	public static String COURSE_DOT_NET_ONLINE = ".NETOnline";
	public static String COURSE_JAVA_OFFLINE = "JavaOffline";
	public static String COURSE_DOT_NET_OFFLINE = ".NETOffline";
}

public abstract class AbstractCourse {
	private String courseName;
	private long courseDuration;
	public abstract void display();
}

public class DotNetOfflineCourse extends AbstractCourse {
	@Override
	public void display() {
		System.out.println("Information of .NET offline course");
	}
}

public class DotNetOnlineCourse extends AbstractCourse {
	@Override
	public void display() {
		System.out.println("Information of .NET online course");
	}
}

public class JavaOfflineCourse extends AbstractCourse {
	@Override
	public void display() {
		System.out.println("Information of Java offline course");
	}
}

public class JavaOnlineCourse extends AbstractCourse {
	@Override
	public void display() {
		System.out.println("Information of Java online course");
	}
}

public abstract class AbstractCourseFactory {
	public AbstractCourse createCourse(String inCourseName) {
		AbstractCourse course = getCourse(inCourseName);
		if(course != null) {
			// Do a bunch of things here, such as initializing some information
			// for the course before returning it to caller
		}
		return course;
	}

	protected abstract AbstractCourse getCourse(String inCourseName);
}

public class OfflineCourseFactory extends AbstractCourseFactory {
	public AbstractCourse getCourse(String inCourseName) {
		AbstractCourse course = null;
		if(inCourseName.equals(CourseConstants.COURSE_JAVA_OFFLINE)) {
			course = new JavaOfflineCourse();
		}
		else if(inCourseName.equals(CourseConstants.COURSE_DOT_NET_OFFLINE)) {
			course = new DotNetOfflineCourse();
		}
		return course;
	}
}

public class OnlineCourseFactory extends AbstractCourseFactory {
	public AbstractCourse getCourse(String inCourseName) {
		AbstractCourse course = null;
		if(inCourseName.equals(CourseConstants.COURSE_JAVA_ONLINE)) {
			course = new JavaOnlineCourse();
		}
		else if(inCourseName.equals(CourseConstants.COURSE_DOT_NET_ONLINE)) {
			course = new DotNetOnlineCourse();
		}
		return course;
	}
}

public abstract class CourseController {
	public void displayCourse(String inCourseName) {
		AbstractCourse course = null;
		AbstractCourseFactory factory = getCourseFactory();
		course = factory.createCourse(inCourseName);
		if(course != null) {
			course.display();
		}
		else {
			System.out.println("Nothing is displayed");
		}
	}
	protected abstract AbstractCourseFactory getCourseFactory();
}

public class OfflineCourseController extends CourseController {
	@Override
	protected AbstractCourseFactory getCourseFactory() {
		return new OfflineCourseFactory();
	}
}

public class OnlineCourseController extends CourseController {
	@Override
	protected AbstractCourseFactory getCourseFactory() {
		return new OnlineCourseFactory();
	}
}

public class FactoryTest {
	public static void main(String[] args) {
		CourseController onlineController = new OnlineCourseController();
		onlineController.displayCourse(CourseConstants.COURSE_DOT_NET_ONLINE);

		CourseController offlineController = new OfflineCourseController();
		offlineController.displayCourse(CourseConstants.COURSE_JAVA_OFFLINE);
	}
}

Output:

Information of .NET online course
Information of Java offline course

Cùng nhìn lại toàn bộ những gì A11 đã làm từ những thiết kế ban đầu cho đến bây giờ. Từ việc chỉ quan tâm đến những khóa học được tao ra, anh ta tạo ra một thiết kế với kiểu kế thừa cha/con, và dùng if/else để tạo ra khóa học và display lên màn hình ứng với lựa chọn của người dùng. Sau đó, khi ngày càng có nhiều khóa học được thêm vào, A11 đã nghĩ ra cách phải loại bỏ những lệnh if/else ra khối xử lý chính, và FactoryMethod pattern được áp dụng để xử lý việc này bằng cách tạo ra lớp CourseFactory để “sinh” ra các khóa học theo yêu cầu, điều này đáp ứng được nguyên lý Open Closed Principle. Khi mà ngày càng có nhiều loại hình khóa học khác được thêm vào như Online, Offline, Corporate, … thì việc phải thay đổi lớp CourseFactory ngày càng thường xuyên hơn. Dẫn đến việc nguyên lý Single Responsibility Principle bị phá vỡ. AbstractFactory chính là hướng giải quyết trong lúc này. AbstractFactory sẽ để cho mỗi nhà máy Factory cụ thể tự “sinh” ra những khóa học đúng với trách nhiệm của nhà máy đó. Và đó là tất cả để chúng ta có được thiết kế cuối cùng.

Mình sẽ kết thúc mẫu thiết kế FactoryPattern tại đây. Nên nhớ rằng FactoryPattern gồm 2 loại: FactoryMethod (một nhà máy đơn giản) AbstractFactory (nhà máy của những nhà máy), tùy theo yêu cầu của hệ thống mà mỗi pattern sẽ được áp dụng.

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

Why extends is evil

Improve your code by replacing concrete base classes with interfaces

The extends keyword is evil; maybe not at the Charles Manson level, but bad enough that it should be shunned whenever possible. The Gang of Four Design Patterns book discusses at length replacing implementation inheritance (extends) with interface inheritance (implements).

Good designers write most of their code in terms of interfaces, not concrete base classes. This article describes why designers have such odd habits, and also introduces a few interface-based programming basics.

Interfaces versus classes

I once attended a Java user group meeting where James Gosling (Java’s inventor) was the featured speaker. During the memorable Q&A session, someone asked him: “If you could do Java over again, what would you change?” “I’d leave out classes,” he replied. After the laughter died down, he explained that the real problem wasn’t classes per se, but rather implementation inheritance (the extends relationship). Interface inheritance (the implements relationship) is preferable. You should avoid implementation inheritance whenever possible.

Losing flexibility

Why should you avoid implementation inheritance? The first problem is that explicit use of concrete class names locks you into specific implementations, making down-the-line changes unnecessarily difficult.

At the core of the contemporary Agile development methodologies is the concept of parallel design and development. You start programming before you fully specify the program. This technique flies in the face of traditional wisdom—that a design should be complete before programming starts—but many successful projects have proven that you can develop high-quality code more rapidly (and cost effectively) this way than with the traditional pipelined approach. At the core of parallel development, however, is the notion of flexibility. You have to write your code in such a way that you can incorporate newly discovered requirements into the existing code as painlessly as possible.

Rather than implement features you might need, you implement only the features you definitely need, but in a way that accommodates change. If you don’t have this flexibility, parallel development simply isn’t possible.

Programming to interfaces is at the core of flexible structure. To see why, let’s look at what happens when you don’t use them. Consider the following code:

f()
{   LinkedList list = new LinkedList();
    <em>//...</em>
    g( list );
}
g( LinkedList list )
{
    list.add( ... );
    g2( list )
}

Now suppose a new requirement for fast lookup has emerged, so the LinkedList isn’t working out. You need to replace it with a HashSet. In the existing code, that change is not localized since you must modify not only f() but also g()(which takes a LinkedList argument), and anything g() passes the list to.

Rewriting the code like this:

f()
{   Collection list = new LinkedList();
    <em>//...</em>
    g( list );
}
g( Collection list )
{
    list.add( ... );
    g2( list )
}

makes it possible to change the linked list to a hash table simply by replacing thenew LinkedList() with a new HashSet(). That’s it. No other changes are necessary.

As another example, compare this code:

f()
{   Collection c = new HashSet();
    <em>//...</em>
    g( c );
}
g( Collection c )
{
    for( Iterator i = c.iterator(); i.hasNext() 😉
        do_something_with( i.next() );
}

to this:

f2()
{   Collection c = new HashSet();
    <em>//...</em>
    g2( c.iterator() );
}
g2( Iterator i )
{   while( i.hasNext() 😉
        do_something_with( i.next() );
}

The g2() method can now traverse Collection derivatives as well as the key and value lists you can get from a Map. In fact, you can write iterators that generate data instead of traversing a collection. You can write iterators that feed information from a test scaffold or a file to the program. There’s enormous flexibility here.

Coupling

A more crucial problem with implementation inheritance iscoupling—the undesirable reliance of one part of a program on another part. Global variables provide the classic example of why strong coupling causes trouble. If you change the type of the global variable, for example, all functions that use the variable (i.e., are coupled to the variable) might be affected, so all this code must be examined, modified, and retested. Moreover, all functions that use the variable are coupled to each other through the variable. That is, one function might incorrectly affect another function’s behavior if a variable’s value is changed at an awkward time. This problem is particularly hideous in multithreaded programs.

As a designer, you should strive to minimize coupling relationships. You can’t eliminate coupling altogether because a method call from an object of one class to an object of another is a form of loose coupling. You can’t have a program without some coupling. Nonetheless, you can minimize coupling considerably by slavishly following OO (object-oriented) precepts (the most important is that the implementation of an object should be completely hidden from the objects that use it). For example, an object’s instance variables (member fields that aren’t constants), should always be private. Period. No exceptions. Ever. I mean it. (You can occasionally use protected methods effectively, but protected instance variables are an abomination.) You should never use get/set functions for the same reason—they’re just overly complicated ways to make a field public (though access functions that return full-blown objects rather than a basic-type value are reasonable in situations where the returned object’s class is a key abstraction in the design).

I’m not being pedantic here. I’ve found a direct correlation in my own work between the strictness of my OO approach, quick code development, and easy code maintenance. Whenever I violate a central OO principle like implementation hiding, I end up rewriting that code (usually because the code is impossible to debug). I don’t have time to rewrite programs, so I follow the rules. My concern is entirely practical—I have no interest in purity for the sake of purity.

The fragile base-class problem

Now, let’s apply the concept of coupling to inheritance. In an implementation-inheritance system that uses extends, the derived classes are very tightly coupled to the base classes, and this close connection is undesirable. Designers have applied the moniker “the fragile base-class problem” to describe this behavior. Base classes are considered fragile because you can modify a base class in a seemingly safe way, but this new behavior, when inherited by the derived classes, might cause the derived classes to malfunction. You can’t tell whether a base-class change is safe simply by examining the base class’s methods in isolation; you must look at (and test) all derived classes as well. Moreover, you must check all code that uses both base-class and derived-class objects too, since this code might also be broken by the new behavior. A simple change to a key base class can render an entire program inoperable.

Let’s examine the fragile base-class and base-class coupling problems together. The following class extends Java’s ArrayList class to make it behave like a stack:

class Stack extends ArrayList
{   private int stack_pointer = 0;
    public void push( Object article )
    {   add( stack_pointer++, article );
    }
    public Object pop()
    {   return remove( --stack_pointer );
    }
    public void push_many( Object[] articles )
    {   for( int i = 0; i &lt; articles.length; ++i )
            push( articles[i] );
    }
}

Even a class as simple as this one has problems. Consider what happens when a user leverages inheritance and uses the ArrayList‘s clear() method to pop everything off the stack:

Stack a_stack = new Stack();
a_stack.push("1");
a_stack.push("2");
a_stack.clear();

The code successfully compiles, but since the base class doesn’t know anything about the stack pointer, the Stack object is now in an undefined state. The next call to push() puts the new item at index 2 (the stack_pointer‘s current value), so the stack effectively has three elements on it—the bottom two are garbage. (Java’s Stackclass has exactly this problem; don’t use it.)

One solution to the undesirable method-inheritance problem is for Stack to override all ArrayList methods that can modify the array’s state, so the overrides either manipulate the stack pointer correctly or throw an exception. (The removeRange()method is a good candidate for throwing an exception.)

This approach has two disadvantages. First, if you override everything, the base class should really be an interface, not a class. There’s no point in implementation inheritance if you don’t use any of the inherited methods. Second, and more importantly, you don’t want a stack to support all ArrayListmethods. That pesky removeRange() method isn’t useful, for example. The only reasonable way to implement a useless method is to have it throw an exception, since it should never be called. This approach effectively moves what would be a compile-time error into runtime. Not good. If the method simply isn’t declared, the compiler kicks out a method-not-found error. If the method’s there but throws an exception, you won’t find out about the call until the program actually runs.

A better solution to the base-class issue is encapsulating the data structure instead of using inheritance. Here’s a new-and-improved version of Stack:

class Stack
{   private int stack_pointer = 0;
    private ArrayList the_data = new ArrayList();
    public void push( Object article )
    {   the_data.add( stack_pointer++, article );
    }
    public Object pop()
    {   return the_data.remove( --stack_pointer );
    }
    public void push_many( Object[] articles )
    {   for( int i = 0; i &lt; o.length; ++i )
            push( articles[i] );
    }
}

So far so good, but consider the fragile base-class issue. Let’s say you want to create a variant on Stack that tracks the maximum stack size over a certain time period. One possible implementation might look like this:

class Monitorable_stack extends Stack
{
    private int high_water_mark = 0;
    private int current_size;
    public void push( Object article )
    {   if( ++current_size &gt; high_water_mark )
            high_water_mark = current_size;
        super.push(article);
    }

    public Object pop()
    {   --current_size;
        return super.pop();
    }
    public int maximum_size_so_far()
    {   return high_water_mark;
    }
}

This new class works well, at least for a while. Unfortunately, the code exploits the fact that push_many() does its work by calling push(). At first, this detail doesn’t seem like a bad choice. It simplifies the code, and you get the derived class version of push(), even when the Monitorable_stack is accessed through a Stack reference, so the high_water_markupdates correctly.

One fine day, someone might run a profiler and notice theStack isn’t as fast as it could be and is heavily used. You can rewrite the Stack so it doesn’t use an ArrayList and consequently improve the Stack‘s performance. Here’s the new lean-and-mean version:

class Stack
{   private int stack_pointer = -1;
    private Object[] stack = new Object[1000];
    public void push( Object article )
    {   assert stack_pointer &lt; stack.length;
        stack[ ++stack_pointer ] = article;
    }
    public Object pop()
    {   assert stack_pointer &gt;= 0;
        return stack[ stack_pointer-- ];
    }
    public void push_many( Object[] articles )
    {   assert (stack_pointer + articles.length) &lt; stack.length;
        System.arraycopy(articles, 0, stack, stack_pointer+1,
                                                articles.length);
        stack_pointer += articles.length;
    }
}

Notice that push_many() no longer calls push() multiple times—it does a block transfer. The new version of Stack works fine; in fact, it’s better than the previous version. Unfortunately, the Monitorable_stack derived class doesn’t work any more, since it won’t correctly track stack usage if push_many() is called (the derived-class version of push() is no longer called by the inherited push_many() method, sopush_many() no longer updates the high_water_mark). Stack is a fragile base class. As it turns out, it’s virtually impossible to eliminate these types of problems simply by being careful.

Note you don’t have this problem if you use interface inheritance, since there’s no inherited functionality to go bad on you. If Stack is an interface, implemented by both a Simple_stack and a Monitorable_stack, then the code is much more robust.

I provide an interface-based solution in Listing 0.1. This solution has the same flexibility as the implementation-inheritance solution: you can write your code in terms of theStack abstraction without worrying about what kind of concrete stack you actually manipulate. Since the two implementations must provide versions of everything in the public interface, it’s much more difficult to get things wrong. I still have the benefit of writing the equivalent of base-class code only once, because I use encapsulation rather than derivation. On the down side, I have to access the default implementation through a trivial accessor method in the encapsulating class. (Monitorable_Stack.push(...) (on line 41) has to call the equivalent method in Simple_stack, for example.) Programmers grumble about writing all these one-liners, but writing an extra line of code is a trivial price to pay for eliminating a significant potential bug.

 Listing 0.1. Eliminate fragile base classes using interfaces

interface Stack {
	void push(Object o);

	Object pop();

	void push_many(Object[] source);
}

class Simple_stack implements Stack {
	private int stack_pointer = -1;
	private Object[] stack = new Object[1000];

	public void push(Object o) {
		assert stack_pointer < stack.length;

		stack[++stack_pointer] = o;
	}

	public Object pop() {
		assert stack_pointer >= 0;

		return stack[stack_pointer--];
	}

	public void push_many(Object[] source) {
		assert (stack_pointer + source.length) < stack.length;

		System.arraycopy(source, 0, stack, stack_pointer + 1, source.length);
		stack_pointer += source.length;
	}
}

class Monitorable_Stack implements Stack {
	private int high_water_mark = 0;
	private int current_size;
	Simple_stack stack = new Simple_stack();

	public void push(Object o) {
		if (++current_size > high_water_mark)
			high_water_mark = current_size;
		stack.push(o);
	}

	public Object pop() {
		--current_size;
		return stack.pop();
	}

	public void push_many(Object[] source) {
		if (current_size + source.length > high_water_mark)
			high_water_mark = current_size + source.length;

		stack.push_many(source);
	}

	public int maximum_size() {
		return high_water_mark;
	}
}

Frameworks

 A discussion of fragile base classes would be incomplete without a mention of framework-based programming. Frameworks such as Microsoft Foundation Classes (MFC) have become a popular way of building class libraries. Though MFC itself is blessedly fading away, MFC’s structure has been ingrained in countless Microsoft shops where programmers assumed that the Microsoft way was the best way.

A framework-based system typically starts with a library of half-baked classes that don’t do everything they need to do, but rather rely on a derived class to provide missing functionality. A good example in Java is the Component‘s paint() method, which is effectively a place holder; a derived class must provide the real version.

You can get away with this sort of thing in moderation, but an entire class framework that depends on derivation-based customization is brittle in the extreme. The base classes are too fragile. When I programmed in MFC, I had to rewrite all my applications every time Microsoft released a new version. The code would often compile, but then not work because some base-class method changed.

All Java packages work quite well out of the box. You don’t need to extend anything to make them function. This works-out-of-the-box structure is better than a derivation-based framework. It’s easier to maintain and use, and doesn’t put your code at risk if a Sun Microsystems-supplied class changes its implementation.

Summing up fragile base classes

In general, it’s best to avoid concrete base classes and extends relationships in favor of interfaces and implements relationships. My rule of thumb is that 80 percent of my code at minimum should be written entirely in terms of interfaces. I never use references to a HashMap, for example; I use references to the Map interface. (I use the word “interface” loosely here. An InputStream is effectively an interface when you look at how it’s used, even though it’s implemented as an abstract class in Java.)

The more abstraction you add, the greater the flexibility. In today’s business environment, where requirements regularly change as the program develops, this flexibility is essential. Moreover, most of the Agile development methodologies (such as Crystal and extreme programming) simply won’t work unless the code is written in the abstract.

If you examine the Gang of Four patterns closely, you’ll see that many of them provide ways to eliminate implementation inheritance in favor of interface inheritance, and that’s a common characteristic of most patterns. The significant fact is the one we started with: patterns are discovered, not invented. Patterns emerge when you look at well-written, easily maintainable working code. It’s telling that so much of this well-written, easily maintainable code avoids implementation inheritance at all cost.

This article is adapted from my forthcoming book, tentatively titled Holub on Patterns: Learning Design Patterns by Looking at Code, to be published by Apress(www.apress.com) this fall.

Allen Holub has worked in the computer industry since 1979. He currently works as a consultant, helping companies not squander money on software by providing advice to executives, training, and design-and-programming services. He’s authored eight books, including Taming Java Threads (Apress, 2000) and Compiler Design in C (Pearson Higher Education, 1990), and teaches regularly for the University of California Berkeley Extension. Find more information on his Website (http://www.holub.com).

From http://www.javaworld.com/

The Law of Demeter Principle

The Law of Demeter Principle

(Tạm dịch là luật cho người thứ 3 – đùa đó)

Một đoạn hội thoại

“Giờ anh tính giải quyết thế nào về chuyện này?”
“Ah… anh…”
“Tôi không biết, chuyện của anh và cô ấy, tốt nhất là anh nên tự giải quyết riêng với cô ấy. Đừng để đến lúc em phải đi nói chuyện với cô ấy thì mọi chuyện đã quá muộn..”
Rốt cuộc thì anh chàng đó vẫn lén lút qua lại với cô nàng thứ 3. Cho đến 1 ngày, cô người yêu bắt gặp.. Everything is broken..

OK, một câu chuyện vui, nếu anh chàng đó có thể giải quyết riêng với cô kia thì mọi chuyện đã không đến nỗi.

Định nghĩa

Hôm nay tôi sẽ nói thêm một nguyên lý nữa về thiết kế hướng đối tượng. Đó là Law of Demeter (LoD). Nguyên lý này chỉ ra rằng các đối tượng chỉ nên biết những đối tượng nó cần biết phải biết.

Đây là loạt bài về các nguyên lý thiết kế hướng đối tượng. Chúng ta đã trải qua 5 nguyên lý SOLID. Sau đây sẽ là một nguyên lý cũng khá hay để giúp chúng ta giải quyết những vấn đề Ở ĐÂY

Một câu chuyện về quản lý dự án

Nghe phức tạp nhỉ. Ví dụ, trong một dự án, phía phát triển dự án gồm có Project Manager, Team leader, một vài Developer và Tester. OK, bây giờ khách hàng bên kia yêu cầu đội dự án phát triển một phần mềm quản lý ABC. Theo lẽ thường thì họ chỉ cần liên hệ với PM để trao đổi yêu cầu và tiến độ dự án, khách hàng không quan tâm đến các Leader hay Developer bên bạn. Điều này có ý nghĩa gì? Thực tế là như vậy, nên chắc chắn nó là đúng (~99.99%). Vậy là 2 bên cũng đã áp dụng LoD vào việc phát triển dự án, khách hàng chỉ biết đến Developer. Giả sử bây giờ khách hàng muốn control tất cả các các member khác, họ sẽ giao yêu cầu cho các Developer và kiểm tra tiến độ của họ. Điều đó cũng không sai, nhưng giả sử phía phát triển dự án có một số sự thay đổi về nhân sự, một số Developer nghỉ và có một số người khác vào thay. Chẳng cần phải tưởng tượng quá nhiều, chúng ta cũng thấy rõ được là sự thay đổi đó sẽ ảnh hưởng đến toàn bộ 2 phía, khách hàng và bộ phận phát triển, cả 2 đều phải cập nhật lại thông tin của sự thay đổi. Điều này là không cần thiết nếu họ áp dụng nguyên lý Law of Demeter.

Demo

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

public class Main {

    public static void main(String[] args) {
        Address address = new Address();
        address.setName("01");
        address.setZipCode("000001");

        House house = new House();
        house.setAddress(address);

        Person person = new Person();
        person.setHouse(house);

        // Print the person zip code
        System.out.println(person.getHouse().getAddress().getZipCode());
    }
}

Đoạn code trên nhìn không có vấn đề gì. Hãy nhìn lại nguyên lý Law of Demeter. Lớp Main muốn in ra ZipCode của 1 người nào đó nhưng nó lại phải “quan tâm” đến nhiều lớp khác như: House, Address. Có thể nó có 1 sự “tight coupling” không nhẹ ở đây. Để in ra ZipCode, Main phải get House của người đó, sau đó lại get ra địa chỉ của House và cuối cùng là get ra ZipCode từ địa chỉ.

Bây giờ là phần của “The Change”. Không lâu sau đó, Boss của bạn muốn bỏ đi lớp Address, thay vào đó lớp House sẽ giữ ZipCode. Với đoạn chương trình hiện tại của chúng ta thì điều đó chẳng có nghĩa lý gì. Nhưng hãy nghĩ đến những cái hố đen hơn nếu như đoạn in ra ZipCode ở trên xuất hiện ở hàng trăm nơi trong chương trình thực sự của bạn, bạn có đủ can đảm để thay đổi hết chúng, và sau đó… test lại..

Resolve problem with Law of Demeter

Person.java

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

public class Person {
	private House house;

    public void setHouse(House house) {
        this.house = house;
    }

    public House getHouse() {
        return house;
    }

    public String getZipCode() {
        return house.getZipCode();
    }
}

House.java

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

public class House {
	private Address address;

    public void setAddress(Address address) {
        this.address = address;
    }

    public Address getAddress() {
        return address;
    }

    public String getZipCode() {
        return address.getZipCode();
    }
}

Address.java

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

public class Address {
	private String name;
    private String zipCode;

    public void setName(String name) {
        this.name = name;
    }

    public void setZipCode(String zipCode) {
        this.zipCode = zipCode;
    }

    public String getName() {
        return name;
    }

    public String getZipCode() {
        return zipCode;
    }
}

Main.java

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

public class Main {

	public static void main(String[] args) {
		Address address = new Address();
	    address.setName("01");
	    address.setZipCode("000001");

	    House house = new House();
	    house.setAddress(address);

	    Person person = new Person();
	    person.setHouse(house);

	    // Print the person zip code
	    System.out.println(person.getZipCode());
	}

}

Bây giờ, khi sếp muốn remove lớp Address ra khỏi hệ thống, việc đơn giản là move ZipCode qua House và delete lớp Address, những phần khác đều không phải “lo”.

Class Diagram:

Vi phạm Law of Demeter

ViolateLoD

Áp dụng Law of Demeter

LoD

Đó là lợi điểm của LoD, nó giúp hệ thống của chúng ta đứng vững trước những thay đổi bằng cách giảm coupling hay còn gọi là cách design loose coupling, mọi sự thay đổi sẽ là nhỏ nhất nếu có thể.

Nhìn thì có vẻ mọi thứ rất đơn giản. Nhưng nếu chúng ta vi phạm những điều rất đơn giản này, nó sẽ kéo theo việc vi phạm những thứ phức tạp hơn. Vì những điều phức tạp đều được xây dựng từ những điều đơn giản nhất như ở trên.

Mình kết thúc bài Law of Demeter ở đây.

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

 

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

The Interface Sergregation Principle

The Interface Segregation Principle

(Nguyên lý phân tách interface)

Đây là nguyên lý thứ 4 trong 5 nguyên lý của thiết kế hướng đối tượng SOLID. Nó giúp giảm sự cồng kềnh, dư thừa không cần thiết cho phần mềm và quan trọng hơn là giảm sự kết dính làm hạn chế tính linh động (flexibility) của phần mềm.

Nguyên lý phát biểu như sau:

Không nên buộc các thực thể phần mềm phụ thuộc vào những interface mà chúng không sử dụng đến.
Khi xây dựng một lớp đối tượng, đặc biệt là những lớp trừu tượng (abstract class), nhiều người thường có xu hướng để cho lớp đối tượng thực hiện càng nghiều chức năng càng tốt, đưa thật nhiều thuộc tính và phương thức vào lớp đối tượng đó. Những lớp đối tượng như vậy được gọi là những lớp đối tượng có interface bị “ô nhiễm” (polluted interface).
Khi một lớp đối tượng có interface bị “ô nhiễm”, nó sẽ trở nên cồng kềnh. Một thực thể phần mềm nào đó chỉ cần thực hiện một công việc đơn giản mà lớp đối tượng này hỗ trợ buộc phải làm việc với toàn bộ interface của lớp đối tượng đó. Đặc biệt đối với lớp trừu tượng có interface bị “ô nhiễm”, một số lớp kế thừa chỉ quan tâm đến một phần interface của nó nhưng bị buộc phải thực hiện việc cài đặt cho cả phần interface không hề có ý nghĩa đối với chúng. Điều dẫn đến sự dư thừa không cần thiết trong các thực thể phần mềm. Quan trọng hơn nữa, việc buộc các lớp kế thừa phụ thuộc vào phần interface mà chúng không sử dụng đến sẽ làm tăng sự kết dính (coupling) giữa các thực thể phần mềm. Một khi sự nâng cấp, mở rộng diễn ra, đòi hỏi phần interface đó thay đổi, các lớp kế thừa này bị buộc phải chỉnh sửa. Điều này làm cho chúng vi phạm nguyên lý Open-Close.

Dưới đây minh họa cho một thiết kế sai khi không sử dụng nguyên lý phân tách interface. Các lớp kế thừa interface phải thực thi các phương thức không cần thiết của interface đó.

public interface Animal {
    void fly();
    void run();
    void bark();
}
public class Bird implements Animal {
    public void bark() { /* do nothing */ }
    public void run() {
        // write code about running of the bird
    }
    public void fly() {
        // write code about flying of the bird
    }
}
public class Cat implements Animal {
    public void fly() { throw new Exception("Undefined cat property"); }
    public void bark() { throw new Exception("Undefined cat property"); }
    public void run() {
        // write code about running of the cat
    }
}
public class Dog implements Animal {
    public void fly() { }
    public void bark() {
        // write code about barking of the dog
    }
    public void run() {
        // write code about running of the dog
    }
}

Hãy tưởng tượng khi interface Animal ngày càng mở rộng, thì các lớp kế thừa phải tiếp tục implement các phương thức không cần thiết. Đã đến lúc nghĩ đến việc phân tách interface thành các interface nhỏ hơn. Mỗi interface chỉ nên làm những nhiệm vụ có mối liên hệ chặt chẽ với nhau.

Dưới đây là thiết kế sử dụng nguyên lý phân tách interface:

public interface Flyable {
    void fly();
}
public interface Runnable {
    void run();
}
public interface Barkable {
    void bark();
}
public class Bird implements Flyable, Runnable {
    public void run() {
        // write code about running of the bird
    }
    public void fly() {
        // write code about flying of the bird
    }
}
public class Cat implements Runnable{
    public void run() {
        // write code about running of the cat
    }
}
public class Dog implements Runnable, Barkable {
    public void bark() {
        // write code about barking of the dog
    }
    public void run() {
        // write code about running of the dog
    }
}

Như vậy, việc mở rộng các interface sẽ không còn là vấn đề.

Bàn thêm vài nguyên lý:

Nguyên lý Phân tách interface có mối liên hệ (nhưng không mật thiết lắm) với nguyên lý Open-Close. Sự vi phạm nguyên lý Phân tách interface có khả năng dẫn đến sự vi phạm nguyên lý Open-Close (xem phân tích ở trên).
Interface bị “ô nhiễm” của lớp đối tượng nên được phân tách ngay khi có thể để tránh khả năng dẫn đến sự vi phạm nguyên lý Open-Close. Việc phân tách interface có thể được thể hiện thông qua việc truyền từng tham số riêng, cụ thể vào một hàm hơn là truyền một tham số chung, tổng quát trong khi hàm đó chỉ sử dụng một phần công việc được hỗ trợ bởi tham số chung, tổng quát này. Nó cũng có thể được thể hiện thông qua việc tăng thêm mức độ trừu tượng trong cây kế thừa. Interface chung của một bộ phận lớp kế thừa được tổng hợp lại trong một lớp cơ sở. Và lớp cơ sở này lại kế thừa từ lớp cơ sở ban đầu. Như vậy những lớp kế thừa khác không bị phụ thuộc vào phần interface mà chúng không sử dụng đến của những lớp kế thừa kia.
Trong trường hợp sau khi phân tách interface, một số lớp kế thừa muốn sử dụng những phần interface đã phân tách, chúng có thể thực hiện việc đa kế thừa từ những lớp hỗ trợ những phần interface này hoặc thực hiện “composition” những đối tượng thuộc những các lớp đó.

(Tổng hợp và bổ sung từ nguồn internet)
Các bạn có thể tham khảo một số bài viết tiếng Anh tại đây:

https://edwardthienhoang.wordpress.com/2015/07/20/parameter-and-return-type-interfaces/
http://efectivejava.blogspot.com/2013/09/interface-segregation-principleisp-java.html
http://codebuild.blogspot.com/2010/09/oop-solid-rules-interface-segregation.html
http://dotnetcodr.com/2013/08/22/solid-design-principles-in-net-the-interface-segregation-principle/
http://www.remondo.net/solid-principles-csharp-interface-segregation/

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

Liskov Substitution Principle

Liskov Substitution (LSP)

Đây là nguyên lý thứ 3 trong SOLID. Nguyên lý này nói rằng các lớp dẫn xuất phải có thể được thay thế bởi lớp cha. Nguyên lý này được Barbara Liskov đề cập lần đầu tiên trong quyển “Data Abstraction and Hiearchy” xuất bản năm 1988. Xin nói thêm rằng Barbara Liskov là người phụ nữ đầu tiên ở Mỹ nhận bằng tiến sĩ trong lĩnh vực công nghệ thông tin.

Nguyên lý Thay thế Liskov phát biểu như sau:
“Lớp D được gọi là kế thừa từ lớp B khi và chỉ khi với mọi hàm F thao tác trên các đối tượng của B, cách cư xử (behavior) của F không đổi khi thay thế các đối tượng của B bằng các đối tượng của D”.

Để hiểu được nguyên lý trên, ta cần xem xét lại khái niệm kế thừa trong lập trình hướng đối tượng. Kế thừa (inheritance) là một trong 3 tính chất cơ bản nhất của lập trình hướng đối tượng (bên cạnh tính đóng gói (encapsulation) và tính đa hình (polymorphism)). Đó là tính chất một lớp có khả năng “tái sử dụng” các thuộc tính và phương thức của lớp khác và quan trọng hơn là nó có thể cư xử (behavior) như lớp đó. Quan hệ “IS A” thường được dùng để xác định kế thừa. A kế thừa B khi A quan hệ “IS A” với B. Có nghĩa A là trường hợp đặc biệt của B.

Nghe thì có vẻ phức tạp, lằng nhằng, nhưng khi nhìn vào thực tế thì nguyên lý này khá đơn giản. Hãy xem xét 1 ví dụ đơn giản sau đây.
Chúng ta biết “hình vuông là hình chữ nhật”. Nhưng hình chữ nhật có thay thế được hình vuông? Hãy xét mối quan hệ kế thừa đơn giản trong hướng đối tượng giữa hình chữ nhật và hình vuông.

// Violation of Likov's Substitution Principle
class Rectangle
{
	protected int m_width;
	protected int m_height;

	public void setWidth(int width){
		m_width = width;
	}

	public void setHeight(int height){
		m_height = height;
	}

	public int getWidth(){
		return m_width;
	}

	public int getHeight(){
		return m_height;
	}

	public int getArea(){
		return m_width * m_height;
	}
}

class Square extends Rectangle
{
	public void setWidth(int width){
		m_width = width;
		m_height = width;
	}

	public void setHeight(int height){
		m_width = height;
		m_height = height;
	}

}

class LspTest
{
	private static Rectangle getNewRectangle()
	{
		// it can be an object returned by some factory ...
		return new Square();
	}

	public static void main (String args[])
	{
		Rectangle r = LspTest.getNewRectangle();

		r.setWidth(5);
		r.setHeight(10);
		// user knows that r it's a rectangle.
		// It assumes that he's able to set the width and height as for the base class

		System.out.println(r.getArea());
		// now he's surprised to see that the area is 100 instead of 50.
	}
}

Ở ví dụ trên, chúng ta đã vi phạm nguyên lý thay thế Liskov bởi vì behavior của Width và Heigh đã bị thay đổi ở lớp Square. Vì vậy, khi thay thế Square bằng Rectangle sẽ dẫn đến kết quả không đúng. (Rectangle phải ra 5×10=50 chứ không phải 10×10=100).

Chúng ta phải bảo đảm rằng, khi một lớp con kế thừa từ một lớp khác, nó sẽ không làm thay đổi hành vi của lớp đó. Hay trong ví dụ này là lớp Square không được phép thay đổi hành vi của lớp Rectangle (Width và Height)

Bây giờ hãy tạo ra một thiết kế mới bằng cách áp dụng nguyên lý thay thế Liskov. Chúng ta có 1 lớp Shape sẽ là lớp cơ sở cho cả 2 lớp Rectangle và Square.

Shape.java

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

/**
 * Class desciption
 * @author Edward
 */
public abstract class Shape {

	/** mHeight */
	protected int mHeight;

	/** mWidth */
	protected int mWidth;

	/**
	 * Get shape's width
	 * @author Edward
	 * @return
	 */
	public abstract int getWidth();

	/**
	 * Set shape's width
	 * @author Edward
	 */
	public abstract void setWidth(int inWidth);

	/**
	 * Get shape's height
	 * @author Edward
	 * @return
	 */
	public abstract int getHeight();

	/**
	 * Set shape's height
	 * @author Edward
	 */
	public abstract void setHeight(int inHeight);

    /**Return area of shape
     * @author Edward
     * @return
     */
    public int getArea() {
        return mHeight * mWidth;
    }
}

Rectangle.java

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

/**
 * Rectangle class
 * @author Edward
 */
public class Rectangle extends Shape
{
	@Override
	public int getWidth() {
		return mWidth;
	}

	@Override
	public int getHeight() {
		return mHeight;
	}

	@Override
	public void setWidth(int inWidth) {
		mWidth = inWidth;
	}

	@Override
	public void setHeight(int inHeight) {
		mHeight = inHeight;
	}

}

Square.java

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

/**
 * Class desciption
 * @author Edward
 */
public class Square extends Shape {

	@Override
	public int getWidth() {
		return mWidth;
	}

	@Override
	public void setWidth(int inWidth) {
		SetWidthAndHeight(inWidth);
	}

	@Override
	public int getHeight() {
		return mHeight;
	}

	@Override
	public void setHeight(int inHeight) {
		SetWidthAndHeight(inHeight);
	}

	/**
	 * Set both width and height with the same value
	 * @author Edward
	 * @param inValue
	 */
	private void SetWidthAndHeight(int inValue) {
        mHeight = inValue;
        mWidth = inValue;
    }

}

Main.java

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

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

	static final String SQUARE = "Square";
	static final String RECTANGLE = "Rectangle";

	/**
	 * Main method
	 * @author Edward
	 * @param args
	 */
	public static void main(String[] args) {
		Shape shape1 = getShape(SQUARE);
		shape1.setHeight(10);
		shape1.setWidth(20);
		System.out.println(SQUARE + "'s area: " + shape1.getArea());

		Shape shape2 = getShape(RECTANGLE);
		shape2.setHeight(10);
		shape2.setWidth(20);
		System.out.println(RECTANGLE + "'s area: " + shape2.getArea());
	}

	/**
	 * Simple factory method
	 * @author Edward
	 * @param inShapeType
	 * @return
	 */
	static Shape getShape(String inShapeType) {
		if(inShapeType.equals(SQUARE)) {
			return new Square();
		}
		if(inShapeType.equals(RECTANGLE)) {
			return new Rectangle();
		}
		return null;
	}

}

Output:
Square’s area: 400
Rectangle’s area: 200

Bây giờ chạy, chúng ta đã có kết quả đúng.

Như vậy chúng ta vừa tìm hiểu xong 1 nguyên lý nữa trong SOLID. Nhìn thì có vẻ đơn giản, nhưng nếu các bạn vi phạm nguyên lý đơn giản này có thể để lại hậu quả không thể lường trước được. Hãy chắc rằng bạn chỉ nên override lại các thuộc tính hay hành vi trừu tượng ở lớp cha vả đảm bảo việc override đó không ảnh hưởng đến các hành vi và phương thức còn lại của lớp cha như ví dụ trên.

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

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