InfoQ’s 2019, and Software Predictions for 2020

See full post at: https://www.infoq.com/articles/infoq-2019-retrospective/

Key Takeaways

  • Last month, Google claimed to have achieved quantum supremacy—the name given to the step of proving quantum computers can deliver something that a classical computer can’t. That claim is disputed, and it may yet turn out that we need a better demonstration, but it still feels like a significant milestone.
  • A surprise this year was the decline of interest in Virtual Reality, at least in the context of Smart-phone-based VR. Despite this we still think that something in the AR/VR space, or some other form of alternative computer/human interaction, is likely to come on the market in the next few years and gain significant traction.
  • We expect to see the interest in Web Assembly continue and hope that the tooling for it will start to mature.
  • In our DevOps and Cloud trend report, we noted that Kubernetes has effectively cornered the market for container orchestration, and is arguably becoming the cloud-agnostic compute abstraction. The next “hot topics” in this space appear to be “service meshes” and developer experience/workflow tooling.
  • We’re looking forward to seeing what the open source community and vendors are working on in the understandability, observability, and debuggability space in the context of architectural patterns such microservices and functions(as-a-service).

Development

Java

JavaScript, Java, and C# remain the most popular languages we cover, but we’re also seeing strong interest in Rust, Swift, and Go, and our podcast with Bryan Cantrill on “Rust and Why He Feels It’s The Biggest Change In Systems Development in His Career” is one of the top-performing podcasts we’ve published this year. We’ve also seen a growing interest in Python this year, probably fuelled by its popularity for machine learning tasks.

After a rather turbulent 2018, Java seems to be settling into its bi-annual release cycle. According to our most-recent reader survey Java is the most used language amongst InfoQ readers, and there continues to be a huge amount of interest in the newer language features and how the language is evolving. We also continue to see strong and growing interest in Kotlin.

It has been interesting to see Microsoft’s growing involvement in Java, joining the OpenJDK, acquiring JClarity, and hiring other well known figures including Monica Beckwith.

In the Java programming language trends report, we noted increased adoption of non-HotSpot JVMs, and we believe OpenJ9 is now within the early-adopter stage. As the time we noted that:

“We believe that the increasing adoption of cloud technologies within all types of organisation is driving the requirements for JREs that embrace associated “cloud-native” principles such as fast start-up times and a low memory footprint. Graal in itself may not be overly interesting, but the ability to compile Java application to native binaries, in combination with the support of polyglot languages, is ensuring that we keep a close watch on this project.”

.NET

The release of .NET Core 3 in September generated a huge buzz on InfoQ and produced some of our most-popular .NET content of the year. WebAssembly has been another area of intense interest, and we saw a corresponding surge in interest for Blazor, a new framework in ASP.NET Core that allows developers to create interactive web applications using C# and HTML. Blazor comes in multiple editions, including Blazor WebAssembly which allows single-page applications to run in the client’s web browser using a WebAssembly-based .NET runtime.

According to our most-recent reader survey, C# is the second-most widely used language among InfoQ readers after Java, and interest in C#8 in particular was also strong.

Web Development

Unsurprisingly, the majority of InfoQ readers write at least some JavaScript – around 70% according to the most recent reader survey – making it the most widely used language among our readers. The dominant JavaScript frameworks for InfoQ readers seem to currently be Vue and React. We also saw interest in using Javascript for machine learning via TensorFlow.js.  Away from JavaScript, we saw strong interest in some of the transpiler options. In addition to Blazor, mentioned above, we saw strong interest in Web Assembly, Typescript, Elm and Svelte.

Architecture

It’s unsurprising that distributed computing, and in particular the microservices architecture style, remains a huge part of our news and feature content. We see strong interest in related topics, with our original Domain Driven Design Quickly book, and our more-recent eMag “Domain-Driven Design in Practice” continuing to perform particularly well, and interest in topics like observability and distributed tracing. We also saw interest in methods of testing distributed systems, including a strong performance from our Chaos Engineering eMag, and a resurgence in reader interest in some for the core architectural topics such as API design, diagrams, patterns, and models.

AI, ML and Data Engineering

Our podcast with Grady Booch on today’s Artificial Intelligence reality and what it means for developers was one of our most popular podcasts of the year, and revealed strong interest in the topic from InfoQ readers.

Key AI stories in 2019 were MIT introducing GEN, a Julia-basd language for artificial intelligence, Google’s ongoing work on ML Kit, and discussions around conversational interfaces, as well as more established topics such as streaming.

It’s slightly orthogonal to the rest of the pieces listed here, but we should also mention “Postgres Handles More Than You Think” by Jason Skowronski which performed amazingly well.

Culture and Methods

If there was an overarching theme to our culture and methods coverage this year it might best be summed up as “agile done wrong” and many of our items focused on issues with agile, and/or going back to the principles outlined in the Agile Manifesto.

We also saw continued in interest in some of the big agile methodologies, notably Scrum, with both “Scrum and XP from the Trenches“, and “Kanban and Scrum – Making the Most of Both” performing well in our books department.

We also saw strong reader interest in remote working with Judy Rees’ eMag on “Mastering Remote Meetings“, and her corresponding podcast, performing well, alongside my own talk on “Working Remotely and Managing Remote Teams” from Aginext this year.

DevOps and Cloud

In our DevOps and Cloud trends report, we noted that Kubernetes has effectively cornered the market for container orchestration, and is arguably becoming the cloud-agnostic compute abstraction. The next “hot topics” in this space appear to be “service meshes” and developer experience/workflow tooling. We continue to see strong interest in all of thee among InfoQ’s readers.

A trend we’re also starting to note is a number of languages which are either infrastructure or cloud-orientated. In our Programming Languages trends report, we noted increased interest and innovation related to infrastructure-aware or cloud-specific languages, DSLs, and SDKs like Ballerina and Pulumi. In this context we should also mention Dark, a new language currently still in private beta, but already attracting a lot of interest. Somewhat related, we should also mention the Ecstasy language, co-created by Tangosol founders Cameron Purdy and Gene Gleyzer. Chris Swan, CTO for the Global Delivery at DXC Technology, spoke to Cameron Purdy about the language and the problems it’s designed to solve.

Software Predictions for 2020

Making predictions in software in notoriously hard to do, but we expect to see enterprise development teams consolidate their cloud-platform choices as Kubernetes adoption continues. Mostly this will be focussed on the “big five” cloud providers – Amazon, Google, IBM (plus Red Hat), Microsoft, and VMware (plus Pivotal). We think that, outside China, Alibaba will struggle to gain traction, as will Oracle, Salesforce, and SAP.

In the platform/operations space we’re expecting that service meshes will become more integrated with the underlying orchestration frameworks (e.g. Kubernetes). We’re also hopeful that the developer workflow for interacting with service meshes becomes more integrated with current workflows, technologies, and pipelines.

Ultimately developers should be able to control deploy, release, and debugging via the same continuous/progressive delivery pipeline. For example, using a “GitOps” style pipeline to deploy a service by configuring k8s YAML (or some higher-level abstraction), controlling the release of the new functionality using techniques like canarying or shadowing via the configuration of some traffic management k8s custom resource definition (CRD) YAML, and enabling additional logging or debug tooling via some additional CRD config.

In regards to architecture, next year will hopefully be the year of “managing complexity”. Architectural patterns such microservices and functions(as-a-service) have enabled developers to better separate concerns, implement variable rates of change via independent isolated deployments, and ultimately work more effectively at scale. However, our ability to comprehend the complex distributed systems we are now building — along with the availability of related tooling — has not kept pace with these developments. We’re looking forward to seeing what the open source community and vendors are working on in the understandability, observability, and debuggability space.

We expect to see more developers experimenting with “low code” platforms. This is partly fueled by a renewed push from Microsoft for its PowerApps, Flow, Power BI, and Power Platform products.

In the .NET ecosystem, we believe that Blazor will keep gaining momentum among web developers. .NET 5 should also bring significant changes to the ecosystem with the promised interoperability with Java, Objective-C, and Swift. Although it is early to say, Microsoft’s recent efforts on IoT and AI (with ML.NET) should also help to raise the interest in .NET development.  Related we expect to see the interest in Web Assembly continue and hope that the tooling hear will start to mature.

Despite the negative news around VR this year, we still think that something in the AR/VR space, or some other form of alternative computer/human interaction, is likely to come on the market in the next few years and gain significant traction, though it does seem that the form factor for this hasn’t really arrived.

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]

Technical Debt và Legacy System

Technical debt – tạm dịch là “Khoản nợ kỹ thuật” được dùng nhiều trong Software Engineering. Theo Henrik Kniberg, những khoản nợ kỹ thuật là bất cứ thứ gì trong việc viết mã khiến bạn chậm lại về lâu dài. Ví dụ như là mã khó đọc, thiếu (hoặc không có) kiểm thử tự động, mã trùng lặp, hoặc sự liên kết lằng nhằng giữa lớp, mô-đun… (Think of technical debt as anything about your code that slows you down over the long term. Hard-to-read code, lack of test automation, duplication, tangled dependencies, etc. Henrik Kniberg).

cycle

Cũng giống như những khoản nợ về tài chính: có vay mới có nợ, có nợ ắt sẽ sinh lãi. Technical debt sinh ra vì nhiều lý do: áp lực kinh doanh, thiếu kỹ năng trong phân tích thiết kế cũng như kỹ năng lập trình, không có các bộ mã kiểm thử dẫn đến việc trì hoãn (hoặc không thể) tái cấu trúc lại mã nguồn… Nếu không được trả, theo thời gian, nợ sẽ đẻ lãi, dẫn đến việc chậm tiến độ, những đoạn mã mới được thêm vào sẽ mất nhiều thời gian và chi phí hơn, mà đa phần trong số đó là chi phí cho việc gỡ rối (debugging) và kiểm thử hồi quy (regression testing).

TechnicalDebt

Nếu bạn đang làm việc với 1 legacy system (có rất nhiều định nghĩa về legacy code, nhưng mình thích nhất định nghĩa của Robert C. Martin: legacy code = code without test) nghĩa là bạn đang mang trên mình món nợ về kỹ thuật. Một đoạn mã được viết cách đây 10 năm cũng gọi là legacy code, một đoạn mã được viết ngày hôm qua cũng gọi là legacy code nếu chúng đều không được “chống lưng” bằng mã kiểm thử. Và nếu bạn vẫn tiếp tục tạo ra những đoạn mã “without test”, cũng tức là bạn đang tự đẻ thêm nợ cho chính system của mình. Điều đó cũng giống như việc bạn thêm vào những đoạn mã khó đọc, mã trùng lặp, hoặc sự kiên kết lằng nhằng giữa lớp, mô-đun… (Có phải mình cũng vừa lặp lại những gì đã nói ở trên?).

Vậy bạn sẽ trả nợ bằng cách nào? Hay thõa hiệp trước món nợ + lãi đang ngày một tăng khi mã mới được thêm vào?
Có hàng tá lý do để biện minh cho việc thõa hiệp, có thể kể ra như: đừng phá vỡ những đoạn mã đã chạy ổn định, hệ thống chúng ta quá phức tạp, do đó cần thêm thời gian để (tìm hiểu) thêm mới hoặc sữa chữa một đoạn mã nào đó (và đảm bảo không phá vỡ những đoạn mã hiện tại). Có một câu ví von khá hay về vấn đề này. “The code may not be pretty, but damnit, it works!” dùng để nói về Duct Tap Programmer – người viết mã chỉ để “chạy được”.
Nếu chọn cách trả nợ, những việc bạn sẽ làm là đừng (hoặc hạn chế) để lại nợ nần cho những thế hệ (mã) phía sau. Và điều đầu tiên đó là Hãy ngừng việc tạo ra những đoạn mã xấu (Stop writing crappy code).

TechnicalDebt-StopWritingCrap-vs-KeepWritingCrap

TDD đã khó, TDD cho legacy system còn khó gấp bội. Bài viết dưới đây của Mark Levison bàn về việc những vấn đề gặp phải khi áp dụng TDD trong Legacy System, qua đó trích dẫn 1 số phương pháp của Keith Ray – XP Coach để làm việc với legacy code nhằm giảm (paying down) những khoản technical debt, dựa trên nền tảng cốt lõi là viết mã sạch, tái cấu trúc mã nguồn và bỏ túi SOLID principles.

Xin trích dẫn nguyên văn bài viết bởi Mark Levison từ http://www.infoq.com/news/2009/11/legacy-code

Allan Baljeu was trying to TDD with a legacy C++ code base, he was running into trouble because:

we end up with classes that don’t fully implement the functionality that’s eventually needed, and when others come around to use those classes, and eventually fuller implementations are required, then it turns out that the original design is not adequate, a new design is required, some expectations (tests) need to change and previous uses of the class need to be updated.

He wondered if Big Design Up Front would help solve the problem. George Dinwiddie, Agile Coach, suggested that Alan’s design was trying to tell him something. You have to pay attention to the fundamentals of clean code. You can look at basic coupling and cohesion (i.e. SOLID).

Mike “Geepaw” Hill, Agile Coach, says that in his years of coaching agile teams, one of the following has been at the root of these problems:

  • team is not yet up to speed on refactoring, so your classes aren’t really
    minimal
  • team is not yet skilled at simplicity, so ditto
  • team is not yet doing aggressive & rapid microtesting (aka unit testing), so changes break tests too often
  • team doesn’t know how to handle cross-team or company-to-public dependencies, e.g. shipping api’s
  • team neither pairing nor open workspacing, dramatically slowing team-wide understanding.
  • team likely has no jiggle-less build
  • team could be using tools from the ’40s

Keith Ray, XP Coach, suggests that with legacy code (i.e. systems with high technical debt) the cost of repaying technical debt dominates the cost of implementing a story. He goes on to offer an approach:

To make the code more well-factored (paying down the technical debt), whenever you need to integrate a new feature into it, you should pay close attention to code smells in both the new code and the old code and consider refactoring to deal with each smell as you recognize it.

You can do refactorings in small safe steps (even in C++) manually. Very closely follow the instructions in Fowler’s book on Refactoring until you learn them by heart. Eclipse with gcc has a few refactorings that actually work: Extract Method and Rename. Rename understands scope, so it is safer than search-and-replace. Extract Method and the other refactorings in Ecipse might be buggy, so be careful when you use them. For things like changing a function signature, “lean on the compiler” to show where changes have to be made.

You also need tests to make sure the refactorings are not damaging the existing features. Feather’s book on working with legacy code has lots of techniques for adding tests to legacy code. On a higher level, code smells are violations of good design principles. For example, the Single Responsibility Principle (SRP) says there should one purpose for every class / method / module. There are principles about coupling and cohesion and managing dependencies, etc. It’s often easier to detect a code smell than it is to apply these abstract principles. “Large Class” and “Large Method” are remedied by “Extract Class” and “Extract Method/Move Method”, though knowing SRP helps in deciding what parts of a class or method should be extracted.

Perhaps the most important design principle is “Tell, don’t ask”: keep functionality and data together…. bad code often has the functionality in one place, and gets the data it needs from other places, creating problems with dependencies and lack of locality — symptomized by “adding a new feature requires changing lots of code”. The code smells “Shotgun Surgery”, “Feature Envy”, “Long Parameter List” are applicable here.

Getting fast feedback will allow more refactoring, which will (eventually) allow faster development of new features. Try to get parallel builds happening (distributed compilation). Try to get smaller source files and smaller header files. Reduce the complexity of header files – use forward declarations, avoid inline code, try to keep only one class per header file / source file. Using the “pimpl” idiom widely can decrease compile time by 10%, but it can also disguise the “Large Class” and “Feature Envy” code smells.

The advantage of refactoring instead of rewriting, is that you always have working code. If your manual and automated tests are good, then you should be able to ship the code, even if it is a half-way state between a bad design and a good design.

Keith also wrote “Refactoring: Small Steps Guaranteed to Help You Clean Up Your Code” an article on refactoring C++ code in Better Software Magazine.

Previously on InfoQ: Dealing with Legacy Code, Uncle Bob On The Applicability Of TDD andMaking TDD Stick: Problems and Solutions for Adopters

Những nguyên tắc, những định luật của lập trình mà chúng ta nên có sẵn trong đầu

Nguồn : http://qiita.com/hirokidaichi/items/d6c473d8011bd9330e63

Người dịch : Phan Hoàng Minh (https://viblo.asia/minhp/posts/wpVYRP2kG4ng)

page0010

Nguyên tắc Demeter

Còn có tên gọi khác là nguyên tắc “càng biết ít càng tốt”.

Demeter là tên gọi của Nữ thần nông nghiệp, cũng là nữ thần phân phát trong thần thoại Hi Lạp. Tên bà được dùng để đánh dấu sự ra đời của nguyên tắc này, đây có thể xem là một triết lý nền tảng của việc lập trình được sinh ra từ một aspect-oriented programming (AOP) project cùng tên.

Quan điểm cơ bản của nguyên tắc này chính là : tối giản sự hiểu biết của 1 object về cấu trúc, thuộc tính của các object khác ngoài nó (bao gồm các thành phần con).

http://en.wikipedia.org/wiki/Law_of_Demeter

Nói một cách đơn giản là không được tiếp xúc với thuộc tính, method của các object khác một cách trực tiếp.

#Vi phạm nguyên tắc Demeter
console.log(aStudent.class.grade)

#Không vi phạm nguyên tắc Demeter
console.log(aStudent.getGrade())

Định luật Wirth

“Software gets slower faster than hardware gets faster” – “Tốc độ tiến hóa của phần cứng không bằng tốc độ thoái hóa của phần mềm.”

http://en.wikipedia.org/wiki/Wirth’s_law

Có lẽ ý chính của nó là : lập trình ngày càng dùng nhiều tài nguyên phong phú nên framework phải luôn tiến hóa để phục vụ cho việc đó. Suy ra, tốc độ phần cứng dù có tang lên đi nữa thì tốc độ phần mềm cũng chẳng hề thay đổi gì.

Định luật Brook

Đây là một định luật dựa trên kinh nghiệm thực tế : “Đưa thêm người vào 1 project đang chậm, sẽ chỉ khiến nó càng chậm hơn.”

Hay có thể nói theo một cách khác nữa là “Tập hợp 9 bà bầu lại cũng không thể khiến đứa trẻ ra đời sau 1 tháng.”

Luận thuyết cơ bản của định luật này là

  • Cần thời gian để quen với project
  • Công sức dành cho việc communication sẽ tăng

http://en.wikipedia.org/wiki/Brooks%27s_law

Định luật Conway

“Organizations which design systems … are constrained to produce designs which are copies of the communication structures of these organizations.”

“Một công ty thiết kế hệ thống thế nào cũng sẽ làm ra những thiết kế giống y hệt với thiết kế hệ thống của chính công ty họ.”

http://en.wikipedia.org/wiki/Conway%27s_law

Nghiên cứu gần đây chỉ ra rằng hệ thống của công ty là nhân tố ảnh hưởng lớn nhất đến vấn đề phát sinh ra bug của sản phẩm.

http://research.microsoft.com/apps/pubs/default.aspx?id=70535

Nguyên tắc bất ngờ nhỏ nhất (least astonishment)

Trong trường hợp trên cùng 1 interface có 2 yếu tố hành xử mâu thuẫn với nhau, hoặc cách hành xử không rõ ràng thì cần phải chọn cách hành xử nào gây bất ngờ ít nhất cho người sử dụng.

http://en.wikipedia.org/wiki/Principle_of_least_astonishment

Đây là 1 nguyên tắc về giao diện người dùng.

Một ví dụ đơn giản :

Trên 1 interface có 2 chức năng :

  • Ấn ctrl+Q để thoát chương trình.
  • Nhập macro (lưu 1 tổ hợp phím mang 1 chức năng nào đó để tiện cho việc sử dụng về sau).

Sẽ có trường hợp user muốn dùng Ctrl+Q cho macro của mình, nên hành xử đúng với nguyên tắc bất ngờ nhỏ nhất chính là : trong khi nhập macro thì ctrl+Q được coi như là tổ hợp phím bình thường, không phải là lệnh tắt chương trình. Đây chính là điều gây bất ngờ ít nhất cho người dùng.

Nguyên tắc Boy Scout

Nguyên tắc của các tổ chức Boy scout chính là : lúc đi phải sạch đẹp hơn lúc đến.

Trong lĩnh vực lập trình thì nguyên tắc đó sẽ được hiểu là “Khi bạn checkin 1 module thì lúc đó nó phải đẹp hơn lúc bạn checkout.”

Nguyên tắc YAGNI

Viết tắt của “You ain’t gonna need it” – Cái (chức năng, phần) ấy rồi sẽ không cần thiết.

Đó là một câu khẩu ngữ nhắc nhở người lập trình rằng trong quy trình eXtreme Programming (lập trình cực hạn) thì : “Chưa phải lúc cần thiết thì chưa được phép làm.”

Nguyên tắc DRY

Viết tắt của “Don’t repeat yourself” – với ý nghĩa là “Đừng lặp lại những gì giống nhau”.

http://en.wikipedia.org/wiki/Don%27t_repeat_yourself

Khi nguyên tắc này được áp dụng tốt, dù ta có thay đổi 1 phần thì những phần không liên quan cũng sẽ không bị thay đổi theo. Hơn nữa, những phần có liên quan sẽ được thay đổi cùng 1 lượt, giúp ích rất nhiều cho cả khâu estimate và khâu thực hiện.

Nguyên tắc KISS

Viết tắt của “Keep it simple, stupid” – “Cứ đơn giản thôi, đồ ngu!”. Đây là 1 triết lí của Hải quân Mỹ.

http://en.wikipedia.org/wiki/KISS_principle

Những triết lý tương tự có thể kể đến là :

Phương châm dao cạo Okham (Okham’s razor) – “Không đưa ra nhiều giả thiết nếu không cần thiết. Cái gì cần ít giả thiết để chứng minh sẽ không thể chứng minh được bằng nhiều giả thiết.”

Albert Einstein – “Làm cái gì cũng nên đơn giản nhất có thể, nhưng đơn giản quá thì không được”.

Leonardo da Vinci – “Đơn giản nhất chính là tinh xảo nhất”.

Antoine de Saint- Exupéry – “Hoàn hảo, không phải là không thêm vào được nữa, mà là không thể bớt đi được nữa”.

Nguyên tắc SOLID

Tập hợp những nguyên tắc trong lập trình hướng đối tượng. Các chữ cái đầu hợp lại thành SOLID.

http://en.wikipedia.org/wiki/SOLID_(object-oriented_design)

SRP (Single Responsibility Principle) – “Một class chỉ được có 1 nhiệm vụ” hay nói cách khác, “nếu muốn chỉnh sửa class thì chỉ được phép có 1 và chỉ 1 lý do”.

OCP (Open/closed principle) – “Mở class khi cần mở rộng nó, đóng class khi cần chỉnh sửa nó”.

LSP (Liskov substitution principle) – “Subtype phải luôn có thể được thay thế bằng supertype”.

ISP (Interface segregation principle) – “Việc dùng nhiều interface cho các client khác nhau, tốt hơn là việc chỉ dùng 1 interface cho cùng lúc nhiều mục đích” hay nói cách khác “Không được phép hạn chế access vào những method mà client không sử dụng”.

DIP (Dependency inversion principle) – “Module tầng trên không được phụ thuộc vào module tầng dưới. Bất cứ module nào cũng phải phụ thuộc vào cái trừu tượng, không phải vào cái cụ thể”.

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.

Parameter and Return Type Interfaces

From http://brendan.enrick.com/post/Parameter-and-Return-Type-Interfaces

Brendan Enrick

Thêm 1 ví dụ về Interface Segregation Principle. Trong bài này, tác giả đề cập đến việc áp dụng Interface Segregation Principle cho 2 trường hợp cơ bản nhất là kiểu dữ liệu của tham số (parameter) được truyền vào trong 1 hàm và kiểu dữ liệu của kết quả trả về (return) từ 1 hàm. Tùy vào mục đích sử dụng hàm mà mình có thể giới hạn tầm hiểu biết (knowdlege) của hàm đối với đối số truyền vào đến mức vừa đủ dùng. 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.

Cùng xem bài viết dưới đây của Brendan Enrick để hiểu rõ thêm về ví dụ này.

At some point, I’m sure you’ve had someone suggest that you use an interface instead of a concrete class as a dependency. Assuming you’re following the Interface Segregation Principle, that could be exactly what you want to do. Well written interfaces can make your code much simpler and more clearly define your dependencies. That’s why Interface Segregation Principle is one of those popular SOLID principles you’re always hearing about.

image

I recently read an interesting tweet from Derik Whittaker talking about using IEnumerable when an ICollection or IList would be the better choice.

Ugg, may want to fix your code rather than do this “// ReSharper disable once PossibleMultipleEnumeration”

This move toward making everything IEnumerable<T> is a trend happening across the C# community. I believe that ReSharper (a tool I use and love) is leading to some of this. When you write a method that takes in a collection as a parameter, and your method does a foreach over that collection, ReSharper will suggest (correctly) to change it to an IEnumerable. This is because you only enumerated it once, and that means that you can be more relaxed in your requirements.

Side Note: IEnumerable is not technically the requirement for a foreach loop.

The problem people run into with IEnumerables is that they’re not all collections. IEnumerable<T> just means that there is a method to get an Enumerator. In simple terms, I mean that it’s possible to walk through the items in the enumerable one at a time. Sometimes that requires work that’s more than constant time to get to the next item in the enumerable class. The comment that Derik mentioned in his tweet is one that tells ReSharper to not warn you about a possible issue. That issue is that you might be doing the work of walking through the enumerable more than once. When it sees an enumerable that is enumerated for a second time, you’ll receive this warning.

Keep Your Method Parameters Permissive

When you’re making a method parameter, you want to make sure that you’re only requiring exactly what you need. In doing this, you’ll likely create interfaces following the Interface Segregation Principle. When dealing with collections, you’re likely to accept an IEnumerable<T> if you only enumerate once. You might accept an ICollection<T> if you need to enumerate a couple of times, allow adding and removing, or need to count the items. If your collection needs random, array-like index access, you should consider using IList<T>.

This allows the caller of your method to give you whatever they have at the time. You really don’t care as long as it has what you need. If their object is an IEnumerable that isn’t also an IList, they can convert it before providing it to you. Working this way makes your API much more flexible, since you’re making your minimum requirements clear.

Return Usable Types From Your Methods

Sadly, I see the reverse being pushed as a positive. You will findinformation telling you to return IEnumerable<T>, so that you can easily change. This, however, means that you’re providing your method’s consumer with no information about the object they’re receiving. If they need access to array-like indexing, they have to callToList() on your return value in order to use it.

Since ICollection<T> implements IEnumerable<T> and IList<T> implements ICollection<T> and IEnumerable<T>, you can return an IList<T> allowing your method’s caller to use IList<T>, ICollection<T>, or IEnumerable<T> depending on their usage. You’re using an interface and still giving the consumer the power of choice. You can avoid tightly coupling to a concrete implementation while still providing a useful return value.

I almost never return IEnumerable<T>. I don’t know that the return value will only be enumerated once. That’s outside my current layer of abstraction, so I shouldn’t be dealing with it. The safe bet is just to return a useful collection if that’s what I have. If my value really is enumerable, but not a collection, I will return an IEnumerable. In all other cases, it should be a more useful interface. lists an collections really are cohesive concepts that should be grouped into one object when needed.

Tản mạn về Dependency Injection

Tản mạn về Dependency Injection

Như thường lệ, blog của mình sẽ rất ít các định nghĩa, rất nhiều code, hình ảnh minh họa và những câu nói dí dỏm. OK, hãy bắt đầu với định nghĩa về Dependency

Dependency

Khi trong class A có sự tồn tại của lớp B, dùng lớp B để làm 1 công việc gì đó, ta nói lớp A đang phụ thuộc vào lớp B. Ví dụ: CustomerRepository.java

package com.edward.tutorial.di.repository;

import java.util.Collection;

import com.edward.tutorial.di.model.Customer;

public class CustomerRepository {
	public Customer findOne(String customerId) {
		// Code stuff
	}

	public Collection<Customer> findAll() {
		// Code stuff
	}

	public void create(Customer customer) {
		// Code stuff
	}

	public boolean update(Customer customer) {
		// Code stuff
	}

	public void delete(Customer customer) {
		// Code stuff
	}
}

CustomerService.java

package com.edward.tutorial.di.service;

import java.util.Collection;

import com.edward.tutorial.di.model.Customer;
import com.edward.tutorial.di.repository.CustomerRepository;

public class CustomerService {
	
	CustomerRepository customerRepository;
	
	Collection<Customer> findAll() {
		return customerRepository.findAll();
	}

	Customer create(Customer customer) {
		customerRepository.create(customer);
		return customer;
	}
	
	Customer update(Customer customer) {
		customerRepository.update(customer);
		return customer;
	}

	void delete(String customerId) {
		Customer customer = findOne(customerId);
		customerRepository.delete(customer);
	}
	
	public Customer findOne(String customerId) {
		return customerRepository.findOne(customerId);
	}
}

Như ví dụ ở trên, lớp CustomerService đang phụ thuộc vào lớp CustomerRepository, vì CustomerService đang sử dụng 1 instance của CustomerRepository để thực hiện việc lưu trữ dữ liệu xuống database, cụ thể ở đây là customerRepository. Dependency1 Vậy: customerRepository được khởi tạo ở đâu? Vì nếu không được khởi tạo, thì khi sử dụng chúng ta sẽ nhận được NullPointerException. Câu trả lời là: để sử dụng được đối tượng customerRepository trong CustomerService, chúng ta sẽ có 2 cách:

  1. Khởi tạo đối tượng customerRepository trong lớp CustomerService.
  2. Truyền (Inject) đối tượng customerRepository đã được khởi tạo sẵn từ bên ngoài vào lớp CustomerService.

Dependency2

Injection

CHÚNG TA VỪA NHẮC ĐẾN INJECT. ĐÚNG VẬY, KHI LÀM THEO CÁCH 2, NGHĨA LÀ CHÚNG TA ĐANG THỰC HIỆN DEPENDENCY INJECTION. MỘT LƯU Ý KHÁC: Nếu chỉ muốn hiểu Dependency Injection là gì thì xin chúc mừng, chúng ta đã hoàn thành mục tiêu ở đây. Tuy nhiên chắc sẽ chẳng ai muốn dừng lại tại đây. Cho nên chúng ta sẽ đi tiếp phần demo và những chủ đề liên quan đến Dependency Injection Tiếp tục với việc demo cho 2 cách trên. Dependency3 Vậy các bạn sẽ tự hỏi: cách nào là tốt nhất, lúc nào nên dùng cách nào? Câu trả lời vẫn như mọi khi: không cách nào là hoàn hảo.

Tightly coupling và Loosely coupling

Trước khi đi vào phân tích ưu nhược của các cách làm, chúng ta cần làm rõ 2 khái niệm: tightly coupling và loosely coupling.

Tightly coupling:

Ở cách 1, khi khởi tạo đối tượng customerRepository ngay trong lớp CustomerService, ta nói lớp CustomerService đang phụ thuộc hoàn toàn vào lớp CustomerRepository. Trong suốt quá trình chương trình được thực thi (runtime), lớp CustomerService sẽ chỉ làm việc với duy nhất thể hiện của lớp CustomerRepository. Ở cách 2, mặc dù chúng ta không khởi tạo trực tiếp đối tượng customerRepository trong lớp CustomerService mà inject từ ngoài vào, nhưng lớp CustomerService vẫn chỉ làm việc được đối tượng customerRepository, do đó, cách 2 vẫn là Tightly coupling. Vậy, tightly coupling là việc một lớp TRỰC TIẾP sử dụng (phụ thuộc) trực tiếp vào thể hiện cụ thể của 1 lớp khác. TightlyCoupling

Loosely coupling:

Trái với tightly coupling, dễ dàng suy ra loosely coupling là việc một lớp KHÔNG TRỰC TIẾP sử dụng (phụ thuộc) vào thể hiện cụ thể của bất cứ 1 lớp cụ thể nào. Cho đến bây giờ vẫn chưa có 1 ví dụ nào để cho thấy loosely coupling như thế nào. Trong Java (hoặc các ngôn ngữ lập trình hướng đối tượng khác), để đạt được loosely coupling trong thiết kế, chúng ta cần sử dụng khái niệm “đa hình” (polymorphism). Tính đa hình trong lập trình hướng đối tượng là việc định nghĩa những đối tượng trừu tượng (abstraction) như abstract class hoặc interface. Những đối tượng abstraction này khai báo hoặc đưa ra quy ước (contract) và cho phép nhiều lớp đối tượng cụ thể kế thừa và định nghĩa lại hành vi cho riêng chúng.

Ví dụ về tính đa hình

Một tổ chức tiêu chuẩn ISO nào đó đưa ra định nghĩa về chiếc điện thoại: điện thoại là vật có khả năng nghe và nhận cuộc gọi. Lúc này ISO chưa hề cho ra đời 1 chiếc điện thoại nào có thể cầm nắm được, túm lại, nó chỉ là định nghĩa, để khi có người đang cầm trên tay 1 chiếc điện thoại thực sự thì họ có thể gọi hoặc nhận cuộc gọi. Sau khi đã có định nghĩa thế nào là điện thoại, các nhà sản xuất điện thoại hàng đầu thế giới mới bắt tay vào việc sản xuất ra những chiếc điện thoại theo tiêu chuẩn đã đặt ra bởi ISO. Hiện tại có 2 nhà sản xuất là Nokia và Samsung. Mục đích của họ là phải sản xuất ra những chiếc điện thoại có thể nghe và gọi được theo tiêu chuẩn ISO. Khi những chiếc điện thoại được tung ra thị trường, người sử dụng sau đó mua về và thực hiện chức năng nghe gọi mà họ đã từng được nghe ISO công bố trước đó. Tuy nhiên khi thực hiện cuộc gọi từ điện thoại Nokia thì sẽ nghe câu “Hello Microsoft” đầu tiên, còn với Samsung sẽ là câu “Lee Yong-dae” chẳng hạn. (I love Badminton). Có một nhà sản xuất nghiệp dư (giấu tên) cũng tập tành sản xuất điện thoại nhưng không tuân theo tiêu chuẩn của ISO, vậy nên khi cho ra thị trường, người ta không thể thực hiện nghe gọi, mà chỉ có thể… nghe nhạc. Vậy nên có thể gọi đây là cái máy nghe nhạc (tuân theo tiêu chuẩn của một tổ chức nào đó dành cho máy nghe nhạc), chứ không phải cái điện thoại. Phần mô tả cho đa hình đến đây là hết. Cười chút đã. =)) Do vậy, một lớp được gợi là loosely couling khi nó chỉ phụ thuộc vào các đối tượng high level (Abstract class hoặc Interface) thay vì phụ thuộc vào các lớp đối tượng cụ thể (concrete class). (Giống như việc một người tuyên bố, tui biết sử dụng tất cả điện thoại, miễn là nó có thể gọi và nghe, chứ không riêng gì cái điện thoại mà cứ phát ra câu “Lee Yong-dae”.) Ví dụ với bài Điện thoại theo chuẩn ISO ở trên

package com.edward.tutorial.di.phone;

public abstract class Phone {
	public abstract void call(String number);
	public abstract void answer(String number);
}

package com.edward.tutorial.di.phone;

public class NokiaPhone extends Phone {

	@Override
	public void call(String number) {
		System.out.println("Microsoft... " + number);
	}

	@Override
	public void answer(String number) {
		System.out.println(number + " Buy me...");
	}
}

package com.edward.tutorial.di.phone;

public class SamsungPhone extends Phone {

	@Override
	public void call(String number) {
		System.out.println("Kpop Kpop Kpop... " + number);
	}

	@Override
	public void answer(String number) {
		System.out.println(number + " I wanna cry...");
	}
}

package com.edward.tutorial.di.phone;

public class User {
	Phone phone;
	
	public User(Phone phone) {
		this.phone = phone;
	}
	
	public void bringMePhone(Phone phone) {
		this.phone = phone;
	}
	
	public void call(String number) {
		phone.call(number);
	}
	
	public void answer(String number) {
		phone.answer(number);
	}
}

package com.edward.tutorial.di.phone;

public class Main {

	public static void main(String[] args) {
		User bill = new User(new SamsungPhone());
		bill.call("0123");
		bill.bringMePhone(new NokiaPhone());
		bill.answer("0124");
	}
}

Quay lại ví dụ của chúng ta Extract CustomerRepository class thành CustomerRepository interface

package com.edward.tutorial.di.repository;

import java.util.Collection;

import com.edward.tutorial.di.model.Customer;

public interface CustomerRepository {

	public abstract Customer findOne(String customerId);

	public abstract Collection<Customer> findAll();

	public abstract void create(Customer customer);

	public abstract boolean update(Customer customer);

	public abstract void delete(Customer customer);

}

CustomerRepositoryImpl sẽ implement CustomerRepository interface

package com.edward.tutorial.di.repository;

import java.util.Collection;

import com.edward.tutorial.di.model.Customer;

public class CustomerRepositoryImpl implements CustomerRepository {
	@Override
	public Customer findOne(String customerId) {
		// Code stuff
	}

	@Override
	public Collection<Customer> findAll() {
		// Code stuff
	}

	@Override
	public void create(Customer customer) {
		// Code stuff
	}

	@Override
	public boolean update(Customer customer) {
		// Code stuff
	}

	@Override
	public void delete(Customer customer) {
		// Code stuff
	}
}

Và inject CustomerRepository interface thay vì CustomerRepositoryImpl

package com.edward.tutorial.di.service;

import java.util.Collection;

import com.edward.tutorial.di.model.Customer;
import com.edward.tutorial.di.repository.CustomerRepository;

public class CustomerService {
	
	CustomerRepository customerRepository;
	
	public CustomerService(CustomerRepository customerRepository) {
		this.customerRepository = customerRepository;
	}
	
	Collection<Customer> findAll() {
		return customerRepository.findAll();
	}

	Customer create(Customer customer) {
		customerRepository.create(customer);
		return customer;
	}
	
	Customer update(Customer customer) {
		customerRepository.update(customer);
		return customer;
	}

	void delete(String customerId) {
		Customer customer = findOne(customerId);
		customerRepository.delete(customer);
	}
	
	public Customer findOne(String customerId) {
		return customerRepository.findOne(customerId);
	}
}

LooselyCoupling

Nên design theo Tightly coupling hay Loosely coupling

Câu trả lời là Loosely coupling. Vì sao? Đọc tiếp…

Loosely coupling = Dependency Injection + Abstraction

Dependency Injection không giúp chúng ta cải thiện tính flexibility trong thiết kế. Tuy nhiên khi chúng ta kết hợp với Abstraction bằng cách inject các đối tượng abtract, thì chúng ta đã có một bản thiết kế loosely coupling và flexibility.

Dependency inversion principle

Dependency inversion principle là 1 trong 5 nguyên lý thiết kế hướng đối tượng (S.O.L.I.D). Xem thêm tại (https://edwardthienhoang.wordpress.com/2013/11/27/the-dependency-inversion-principle/) Nói đơn giản, Dependency inversion principle giúp chúng ta có được 1 thiết kế loosely coupling Dependency inversion principle = Loosely coupling = Dependency Injection + Abstraction

Inversion of Control (IoC)

Nếu như Dependency inversion là principle thì Inversion of Control là pattern tuân theo principle này. Trong Java, IoC pattern xuất hiện khá nhiều. Lấy 1 ví dụ rất quen thuộc, đó là hàm public static void main(String[] args)

package com.edward.tutorial.di;

public class Main {

    public static void main(String[] args) {
        System.out.println("Developer's dream starts from here");
        doMyWish();
    }

    private static void doMyWish() {
        System.out.println("I wish this app would be the most favourite one in the world");
    }
}

Tất cả các lập trình viên Java đều quá quen thuộc với hàm main này – entry point của 1 chương trình Java truyền thống. Lần này chúng ta sẽ không bàn đến cú pháp, mà bàn đến một việc mà được cho là “chẳng ai quan tâm là mấy”. Câu hỏi là: hàm doMyWish() được gọi bởi hàm main(), vậy hàm main() được gọi bởi hàm nào? Câu trả lời là: Java sẽ gọi hàm main() của chúng ta. Vậy làm thế nào mà Java (chính xác hơn là javac.exe hay đại loại là một cái cao siêu gì đó) có thể gọi được hàm main của chúng ta?

Hollywood principle: “Don’t call us, we’ll call you.”

Dịch nôm na: “Đừng có gọi cho tui, để lại số điện thoại, tui sẽ gọi bạn.” Ngôi sao mà, có quyền chảnh. Nếu nói Java là 1 ngôi sao Hollywood, và hàm main là 1 ai đó, thì việc chúng ta cần làm là nói với ngôi sao đó: “Đây! Địa chỉ của tui đây”. Cung cấp cho Java đường dẫn tới class com.edward.tutorial.di.Main chứa hàm main. Việc còn lại cứ để Java lo, Java sẽ gọi hàm main trong class com.edward.tutorial.di.Main và “Developer’s dream” will be started. LƯU Ý: Hollywood principle chỉ là cách gọi khác của Dependency inversion principle. Thêm 1 ví dụ dễ thấy.

JButton helloButton = new JButton("Hello");
helloButton.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("I would say Hello to everyone!");
    }
});

Chúng ta không hề gọi hàm actionPerformed(ActionEvent e), mà chỉ khai báo như vậy, và nói: “Hey! Java Swing or EventDispatchThread or something like that, call me when someone click the helloButton”. Một lần nữa, Java sẽ lo việc đó.

Inversion of Control Container (IoC Container)

Chúng ta đã thấy sự tuyệt vời với những đoạn code ở trên, Inversion of Control Container chính là những ngôi sao Hollywood thực thụ, họ định nghĩa ra Hollywood principle, công việc của lập trình viên là: viết ra những đoạn code và nhờ IoC Container đâu là điểm entry point Trong Eclipse, chúng ta dễ dàng config entry point cho 1 chương trình Java tại: Config_Main

Inversion of Control Container Framework (IoC Container Framework)

Trong ứng dụng Java truyền thống, IoC Container có thể được hiểu như những ví dụ ở trên. Tuy nhiên, trong những ứng dụng Java Enterprise, IoC Container mới có nhiều đất diễn, có khá nhiều Framework hỗ trợ IoC Container tạm gọi là IoC Container Framework như: JEE Framework hoặc Spring Framework. Nơi mà những khái niệm về: Dependency Injection, Inversion of Control hay Inversion of Control Container sẽ được nhắc đến như là những Core Feature. Tất cả đều hướng đến một mục đích duy nhất: tạo ra ứng dụng loosely coupling, flexibility cũng như giúp lập trình viên tập trung 99% vào công việc cho business flow.

Loosely coupling và Testing

Một lợi ích không thể nhắc đến khi thiết kế chương trình theo loosely coupling style chính là Testing. Thay vì phải inject những đối tượng thực vào trong SUT (System under test, hay nôm na là class cần test), chúng ta có thể inject các stub đã implement interface vào trong SUT. Chi tiết sẽ được bàn trong bài viết về UnitTesting. 🙂

Learn from Question&Answer

Có một bạn hỏi như sau:
Nói về chủ đề này, với DI thì ok. Bài viết này của anh khá sâu, tuy nhiên phần ioc lại chưa đc rõ rằng lắm. Vẫn còn khá chung chung. A có thể giải thích cho em vài vấn đề sau. Giữa DI và ioc
1. Thằng nào là con thằng nào?. Quan điểm con của nhau có đúng ko?.
2. Theo em đc biết, DI là 1 design pattern xây dựng lên để đáp ứng cho tính lỏng lẻo, giảm sự liên kết phụ thuộc và khi đó IoC chính là thằng thực hiện theo cái DI đó, nó sẽ sử dụng BeanFactory và ApplicationContext để thao tác lấy thông tin từ XML xử lý, tạo đối tượng. Khi đó, với quan điểm của em thì em nghĩ chẳng thằng nào là con thằng nào cả.

Answer:
Một câu hỏi khá hay. Trong cái thắc mắc của em đã trả lời luôn rồi.
Đúng như em nói: DI là 1 Design Pattern “để đáp ứng cho tính lỏng lẻo, giảm sự liên kết phụ thuộc”. IoC thì rộng hơn, và thường đi liền với Framework. Framework sẽ invert và control phần xương sống của application được develop bên trên, và “chừa”, “cung cấp” 1 số “cổng” để developer có thể inject phần implement của mình vào application. (Inject ở đây có 2 cách hiểu như 2 ví dụ dưới đây.

1. BeanFactory và ApplicationContext: em đang nói đến Spring Framework, vậy đó, Spring cho phép em inject các implementation của mình vào application qua ApplicationContext và 1 số Bean XML. Rõ ràng, DI được dùng để làm chuyện này thông qua @Autowired hay @Inject (CDI)

2. Tuy nhiên, trong bài viết của anh còn đề cập đến một chuyện đơn giản hơn. Đó là Java Swing Framework. Swing cho phép chúng ta định nghĩa ra “những action to be peformed” khi 1 event cụ thể được fired. Còn việc fire đó sẽ do Swing chứ ko phải do mình. DI không được dùng trong đây, nghĩa là Swing không cần đến pattern DI mà vẫn làm được việc Invertion of Control.
Từ 2 ví dụ trên có thể cho em hiểu Invertion Of Control là gì và mối quan hệ với DI rồi nhé.

Kết thúc bài tại đây, hi vọng trong giới hạn hiểu biết cùng với cách giải thích của mình sẽ giúp các bạn có thêm 1 cái nhìn khác về các khái niệm: Dependency Injection, Dependency Inversion Principle, IoC.. Trong những bài sau, chúng ta sẽ bàn luận về IoC Container trong Spring Framework. Happy learning

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