Thứ Sáu, 28 tháng 12, 2018

Facade Pattern

Đây là 1 pattern rất dễ, cái khó nhất có lẽ là đọc cho đúng tên pattern. =)) Facade không đọc là "pha cây" , đọc đúng phải là "phờ sát" (fəˈsɑːd).
oke. Vậy pattern này là gì. Đơn giản lắm! Bạn thử tưởng tượng trong quá trình bạn code, bạn sẽ phân chia các class khác nhau. Theo thời gian thì sẽ có rất nhiều class khác nhau, và các class này sẽ tương tác qua lại với nhau. Điều này thật sự rất rối rắm.
Và để giảm bớt sự rối rắm này, Facade pattern được áp dụng vào. Nó sẽ tạo ra 1 lớp Facade bao bọc bên ngoài, và xử lý những kết nối rối rắm dùng chúng ta.
Facade Pattern: tạo ra 1 giao diện cao hơn giúp người dùng dễ dàng sử dụng các giao diện thấp
Okie. Bây giờ ta vào ví dụ và code nào. Giả sử ta đang thiết kế 1 chiếc xe hơi chẳng hạn. Chiếc xe hơi thì có thể rẻ phải rẻ trái. Vậy lúc rẻ thì nó sẽ làm gì?
Đầu tiên nó sẽ nhả chân côn để mô tơ của xe chạy chậm lại tránh cho xe bị lật khi qua. Sau đó nó sẽ xoay vô lăng để cho bánh xe rẻ qua 1 bên. Đếm sơ sơ qua là ta có 4 class rồi đấy.
public class Wheel {   
   public void turnLeft() {
      // re trai
   }
   public void turnRight() {
      // re phai
   }
}
public class Steering {  
   private Wheel wheel = new Wheel(); 
   // xoay vo lang nguoc kim dong ho
   public void rotateLeft() {
      this.wheel.turnLeft();
   }
   // xoay vo lang cùng chieu kim dong ho
   public void rotateRight() {
      this.wheel.turnRight();
   }
}
public class Motor{   
   public void speedUp() {
      // tang toc
   }
   public void slowDown() {
      // giam toc
   }
}
public class Pedal {  
   private Motor motor = new Motor();    
   public void pressDown() {
      this.motor.speedUp();
   }
   public void release() {
      this.motor.slowDown();
   }
}
Thay vì mỗi lần rẻ trái, chúng ta phải xử lý rối rắm các class trên thì thay vào đó, ta sẽ tạo 1 cái facade Car cho dễ xài.
public class Car{   
   private Steering steering = new Steering();
   private Pedal pedal = new Pedal();
   public void turnLeft() {
      this.steering.rotateLeft();
      this.pedal.release();
   }
   public void turnRight() {
      this.steering.rotateRight();
      this.pedal.release();
   }
}
Giờ muốn xe rẻ phải thì chỉ cần.
Car car = new Car();
car.turnRight();
Share:

Adapter Pattern

Mấy năm gần đây, Apple loại bỏ jack tai nghe rồi, chắc mọi người cũng biết. Vậy mọi người có biết nếu ko có jack tai nghe, thì làm sao để nghe được bằng tai nghe có dây bình thường không? Rất đơn giản, người ta chế ra 1 cục chuyển đổi từ đầu cắm jack tai nghe thành đầu sạc iphone, và cái cục này có thể gọi nó là Adapter.
Trong design pattern, chúng ta cũng có 1 khái niệm tương tự. Chúng ta có 1 đoạn class A, muốn xài 1 method x của class B. Nhưng vì lí do nào đó, A không thể xài B được.
Thì chúng ta sẽ tạo ra 1 adapter trung gian là class C. Và nhờ vào class C, A có thể xài được B.
Adapter Pattern: cho ta 1 lớp có thể xài 1 class khác dựa vào interface của nó
Vấn đề ở đây là, tại sao lại có chuyện A không thể xài B được? Chỉ cần khởi tạo B trong A và xài thôi, có gì khó khăn đâu?
Nhưng nếu bạn làm dự án thực tế, bạn sẽ gặp phải những trường hợp mà bạn ko có quyền sửa code. Tức là project của bạn có xài class A, nhưng bạn không có quyền sửa code của A. Lí do là vì, A có thể là thư viện có sẵn, hoặc A có thể là code của một nhóm khác lập trình....
Pattern này quá đơn giản, nên mình sẽ đi vào code luôn.
public class A {
   private ICanUse canUseInterface;
   public A (ICanUse canUseInterface) {
      this.canUseInterface = canUseInterface;
   }
   private void doSomething() {
      this.canUseInterface.request();
   } 
}
Đầu tiên, ta thấy ở trên, ta có class A đã được lập trình sẵn là xài hàm request của ICanUse. Nhưng bây giờ ta lại muốn A xài được hàm specificRequest của interface B dưới đây.
public Interface B {   
   public void specificRequest();
}
public class randomB implements B {
   public void specificRequest() {
      //do something 
   }
}
Để làm được điều này, ta tạo 1 cái Adapter
public class Adapter implements ICanUse {   
   private B b;
   public A (B b) {
      this.b = b;
   }
   public void request() {
       b.specificRequest();
   }
}
Vậy thôi, giờ muốn A có thể xài được specificRequest của B thì chỉ cần.
A a = new A(new Adapter(new B()));
a.request();
Share:

Command Pattern

Thế giới đã đi vào giai đoạn công nghệ bốn chấm không, mọi thứ đều là công nghệ, áp dụng công nghệ vào tất cả mọi thứ trong cuộc sống. Và hiển nhiên, IoT , internet of thing là một xu hướng tất yếu. Và bạn chuẩn bị khởi nghiệp với công ty iot, sản xuất thiết bị thông minh điều khiển mọi thứ trong nhà. Ồ, nghe xịn sò nhể. Cơ mà đầu tiên phải thử làm một sản phẩm đề mô nhỏ nhỏ trước xem có khả thi không đã nào.
Thử tưởng tượng xem, nếu mình dấn thân vào lĩnh vực Smart Home, kết nối tất cả thiết bị ở trong nhà thì nên bắt đầu như nào nhỉ? À đúng rồi, trước hết phải có 1 cái remote dùng để điều khiển. Remote này có thể điều khiển bóng đèn tắt mở nên nó sẽ có 2 cái nút, 1 nút để tắt, 1 nút để mở.
Nhưng trong nhà thì có nhiều loại bóng đèn lắm, phòng khách có đèn chùm, nhà vệ sinh có đèn huỳnh quang, phòng ăn có đèn sợi tóc... cả tá đèn thì lập trình thế quái nào được đây.
A, đúng rồi. Học strategy pattern rồi, áp dụng vào thôi! Đơn giản quá đi mà.
Mình sẽ tạo 1 interface là ILight, interface này có 2 chức năng là on và off. Từ đó có thể tạo bao nhiêu hiện thực của nó tùy thích.
Bây giờ rất đơn giản, chỉ cần ghán 1 nút của remote chạy hàm on, 1 nút chạy hàm off là được. Muốn điều khiển bóng đèn nào thì chỉ cần set hiện thực của nó vào remote là được. Đơn giản.
Nhưng, ngoài bóng đèn ra còn có nhiều cái khác nữa mà. Ví dụ muốn điều khiển cả tivi nữa. Bấm 1 nút là chuyển kênh, bấm 1 nút là tăng âm lượng. Hừm, ok. Mình sẽ áp dụng strategy pattern tương tự cho tivi để phù hợp với các hãng tivi khác nhau như samsung, sony, lg.... Sau đó gia công thêm vài nút lên cái remote, rồi cài đặt cho những nút đó là xong.
Nhưng trong nhà lại có thêm máy giặt, tủ lạnh, quạt điện.... với biết bao nhiêu là chức năng cần thực hiện. Mà mỗi vật như thế thì có biết bao nhiêu là loại khác nhau. Giờ làm sao đây nhỉ? Nếu cứ thiết kế theo kiểu trên chắc người ta cười vào mặt công ty mình mất.
Và đây chính là lúc, chúng ta nên áp dụng Command Pattern.
Command Pattern: đóng gói mỗi yêu cầu thành những đối tượng để bạn có thể tương tác với những đối tượng bằng những yêu cầu khác nhau. Thể loại Pattern này cũng cho phép ta thực hiện được cái hành động undo nữa
Okie! như thường lệ, ví dụ sẽ làm sáng tỏ định nghĩa.
Với ví dụ smart home ở trên, thay vì ta gộp từng loại thiết bị theo strategy pattern, chúng ta sẽ phân chia mỗi yêu cầu thành từng đối tượng. Có nghĩa rằng, với yêu cầu bật đèn - nó sẽ là 1 đối tượng. Yêu cầu tắt đèn - nó cũng sẽ là 1 đối tượng. Yêu cầu chuyển kênh, tăng âm lượng, giặt áo quần, sấy áo quần, bật quạt, tắt quạt.... - mỗi yêu cầu như thế sẽ là 1 đối tượng.
Và bây giờ, những nút bấm của remote không cần phải gán từng chức năng riêng lẽ như on off nữa, mà nó sẽ được gắn vào những đối tượng đã nêu trên. Ví dụ, nếu gọi cái remote là invoker, các thiết bị của chúng ta là receiver, chúng ta sẽ có mô hình Command pattern sau đây.
Quá trình hoạt động sẽ như sau. Đầu tiên ta sẽ set nút bấm trên remote sẽ làm command nào. Sau đó, khi ta bấm nút, class command sẽ trigger hoạt động execute để gọi tới action của thiết bị. Vậy thôi.
Để rõ hơn, ta đi vào vài dòng code nhé. Đầu tiên là ta có các thiết bị.
public interface Light {
   public void turnOn();
   public void turnOff();
}
public interface Tivi{
   public void nextChannel();
}
public class PhilipLight implements Light{
   public void turnOn() {
       // bat bong den
   }   
   public void turnOff() {
       // tat bong den
   }
}
public class SamsungTivi implements Tivi{
   public void nextChannel() {
       // chuyen kenh
   }   
}
Tiếp đến ta tạo interface cho ICommand và các hiện thực của nó.
public interface ICommand {
   public void execute();
   public void undo();
}
public class TurnLightOnCommand implements ICommand {
   private Light lightl
   public void TurnLightOnCommand (Light light) {
       this.light = light;
   }
   public void execute() {
       this.light.turnOn();
   }
   public void undo() {
      // redo turn on.
   }
}
public class SwitchChannelCommand implements ICommand {
   private Tivi tivi
   public void TurnLightOnCommand (Tivi tivi) {
       this.tivi = tivi;
   }
   public void execute() {
       this.tivi.nextChannel();
   }
   public void undo() {
      // redo next channel.
   }
}
Tiếp đến bạn tạo ra invoker cho cái điều khiển.
public class Invoker {
   private ICommand command;
   public void setCommand(ICommand command) {
       this.command = command;
   }
   public void press() {
      this.command.excute();
   }
}
Bây giờ giả sử bạn muốn nút bấm trên remote của bạn dùng để bật bóng đèn Philip ngoài phòng khách chẳng hạn. Bạn chỉ cần:
ICommand command = new TurnLightOnCommand(new PhilipLight());
Invoker invoker = new Invoker();
invoker.setCommand(command);
Và khi bạn ấn nút thì chạy dòng code sau.
invoker.press();
Vậy là xong. Đó chính là Command Pattern. Còn về phần mà undo tác vụ ấy. Chúng ta hãy liên tưởng tới các phần mềm kiểu như photoshop, khi chúng ta thực hiện thao tác nào đó, thì hãy xem mỗi thao tác đó là 1 command. Ví dụ ta vẽ 1 đường thẳng, chính là 1 command, chúng ta đổi màu cũng là 1 command.... Để tổ chức các command đó thì chúng ta có thể dùng stack để lưu, và khi ta ctrl + Z, thì chỉ cần bóc cái command trong stack ra và chạy hàm undo là được. Rất đơn giản.
Share:

Singleton Pattern

Singleton Patern, một pattern supper thú vị sắp được giới thiệu với bạn đây. Tại sao lại thú vị ư? Để mình nói cho bạn nghe nhé: singleton pattern có hàm constructor là private. Nhớ nhé, private đấy! Không phải public đâu. Vậy tại sao lại là private, chúng ta phải đến với tình huống cụ thể mới biết hiểu được nhé.
Okie, giả dụ bạn là một nhà lập trình trang web gạ "cờ hịch" online. Những người nào muốn được cờ hịch free thì sẽ lên trang web của bạn tìm đối tượng. Vậy đầu tiên chúng ta sẽ phải có 1 cái gì nhỉ? À, chúng ta phải có 1 cái room chát tổng, để cho những con trống vào gạ gẫm những con mái, hoặc con trống gạ con trống ._.. Nói chung là ko quan trọng, quan trọng là chúng ta phải có 1 cái room chat tổng để tất cả mọi người cùng vào. Và trang web của chúng ta có rất nhiều theme nhé, có theme hồng cánh sen cho mấy chị bánh bèo, có theme đen mạnh mẽ cho mấy anh 6 múi.... Nhưng dù ở theme nào đi nữa, chúng ta cũng chỉ sử dụng chung 1 cái room chat duy nhất mà thôi, đó là room chat tổng.
Vậy làm sao để từ 1 class room chat, chúng ta chỉ có thể tạo 1 đối tượng room chat duy nhất, mà không thể tạo ra 1 cái room chat thứ 2. Nghe có vẻ khoai đúng ko? Nếu đã là 1 lớp, thì chúng ta làm quái nào mà ngăn người ta tạo ra nhiều hơn 1 thực thể được. Và đây chính là lúc Singleton Pattern được sử dụng.
Singleton Pattern: đảm bảo rằng 1 lớp chỉ có 1 thực thể duy nhất và cho phép ta truy cập ở cấp độ toàn cục
Tức là sao? Tức là: chúng ta có 1 class A nào đó chẳng hạn, thì nếu thiết kế theo singleton pattern, chúng ta không thể nào tạo ra được 2 thực thể khác nhau của A. Nghe có vẻ lạ đúng không, nhưng điều này là hoàn toàn có thể. Bằng cách nào? Haha, đây là lúc bạn nhìn thấy private constructor. Nghe thôi đã thấy hài vl =)) Cấu trúc của 1 singleton pattern.
1 class A thiết kế theo của singleton pattern, sẽ có 1 biến static có kiểu A, 1 hàm constructor private, và 1 hàm createSingleton static. Vì constructor là private, nên chúng ta không thể tạo thực thể cho class bằng lệnh new A(); được, mà thay vào đó, chúng ta sẽ phải dùng hàm createSingleton để khởi tạo 1 thực thể. Và tất nhiên, trong hàm createSingleton này, chúng ta sẽ trả về 1 biến static duy nhất. Điều này ngăn cản việc khởi tạo 2 thực thể khác nhau của 1 lớp.
OK! Vào code để xem thằng singleton này nó được code như nào xem nào. Đầu tiên là khởi tạo 1 lớp ChatRoom, với thiết kế của singleton như trên.
public class ChatRoom {
   private static ChatRoom chatRoom;
   private ChatRoom() {
   }
   public static ChatRoom createChatRoom(){
   
   }
}
Bây h ta chỉ cần viết logic cho createChatRoom là xong.
public class ChatRoom {
   private static ChatRoom chatRoom;
   private ChatRoom() {
   }
   public static ChatRoom createChatRoom(){
       if(chatRoom == null) {
           chatRoom = new ChatRoom();
       }
       return chatRoom;
   }
}
Vậy là xong. Nếu bạn muốn khởi tạo 1 ChatRoom thì chỉ cần.
ChatRoom chatRoom = ChatRoom.createChatRoom();
Lần thứ 2 bạn khởi tạo ChatRoom bằng câu lệnh trên, thì nó vẫn trả về cho bạn đúng 1 thực thể duy nhất. Và đó chính là Singleton Pattern. Chúc trang web gạ cờ hịch của bạn không bị công an bắt và kiếm được nhiều tiền nhé!
Share:

Thứ Năm, 27 tháng 12, 2018

Abstract Factory Pattern

Nếu bạn đã được đọc về Factory Method Pattern, thì mình nghĩ Abstract Factory Pattern chỉ là 1 cái trường hợp đặc biệt của Factory Method Pattern mà thôi.
Abstract Factory Pattern: cung cấp 1 interface cho phép người dùng tạo ra 1 nhóm các đối tượng độc lập hoặc có liên hệ với nhau mà không cần quan tâm tới lớp thực thi của chúng
Nghe có vẻ bù cả đầu đúng không? Nhưng mà sự thật thì cực kì đơn giản.
Quay lại với ví dụ trong bài Factory Method Pattern, chúng ta có cái game phi thuyền bắn súng đấy. Bạn để ý nhé, trong game lúc nào cũng có background (tức là cái hình nền ấy) nó xuất hiện khác nhau với mỗi level. Ví dụ với level 1, mục tiêu là bắn các mảnh thiên thạch thì background sẽ là bầu trời đêm chẳng hạn. Level 2 bắn phá các phi thuyền thì background sẽ là các linh kiện máy móc... vân vân và mây mây. Và tất nhiên, không phải background của chúng ta là random được, nó phải có lý với bối cảnh của enemy. Không phải tự dưng enemy là các mảnh thiên thạch, mà background lại là đồng cỏ được, nó không có phù hợp gì hết. Hay là enemy là mấy con bò, mà background lại là trên dải ngân hà, đúng lafk hông hợp lý. Vậy nên, ứng với mỗi enemy, chúng ta phải có 1 background đi kèm. Vậy để làm được thế thì ta không thể tạo 2 cái Factory riêng biệt cho enemy và background được. Vì có thể, trong tương lai, không những chỉ có background mà có thể có các hiệu ứng mây mưa, sương khói các thứ cũng phải làm sao cho hợp lý. Như vậy sẽ dẫn tới chúng ta có vô vàn Factory riêng lẻ. Và Abstract Factory Pattern lúc này mới được đưa vào sử dụng.
Cùng xem lại mô hình củ Factory Method Pattern nhé.
Ta thấy, mỗi factory sẽ tạo cho chúng ta 1 đối tượng là IProduct. Nhưng ở bài toán hiện tại, chúng ta không chỉ có 1 product (là enemy) nữa, mà chúng ta có đến 2, hoặc 3 đối tượng cơ. Vậy nên, Abstract Factory Pattern cải tiến lại, giúp cho Factory có thể cho ra được 2 nhiều hơn 1 product.
Ví dụ ta có 2 interface IProductEnemy và IProductBackground, mô hình cảu Abstract Factory Pattern sẽ như sau:
Ta thấy, bây h factory của chúng ta đã có thể cho ra 2 product. Và tất nhiên, khi hiện thực IFactory, thì chúng ta cũng đã định nghĩa được những background nào nên đi kèm với enemy nào rồi, nên sẽ ko có hiện tượng bất hợp lý ở UI khi người ta chơi game cả. Vậy thôi, đó là Abstract Factory Pattern.
Bây h thử code vài dòng để hiểu hơn nó là như thế nào nà. Đầu tiên vẫn như cũ, ta tạo interface của enemy và các hiện thực của nó.
public interface Enemy{
    public void speed();
    public void healthy();
} 
public interface FirstLevelEnemy extends Enemy{
} 
public interface SecondLevelEnemy extends Enemy{
}
public class BigMeteorite implements FirstLevelEmemy {
   public void speed() {
       //lam cai gi do
   }
    public void healthy(){
       //lam cai gi do
   }
}
public class VietNamSpaceShip implements SecondLevelEnemy {
   public void speed() {
       //lam cai gi do
   }
    public void healthy(){
       //lam cai gi do
   }
}
Bây giờ chúng ta tạo ra interface cho background và các hiện thực của nó.
public interface Background{
    public void display();
} 
public interface FirstLevelBackground extends Background{
} 
public interface SecondLevelBackground extends Background{
}
public class DarkGalaxy implements FirstLevelBackground {
   public void display() {
       //lam cai gi do
   }   
}
public class OldShipFLoor implements SecondLevelBackground {
   public void display() {
       //lam cai gi do
   } 
}
Tiếp đến ta tạo ra interface Factory và các hiện thực của nó.
public interface IFactory{
    public Enemy createEnemy();
    public Background createBackground();
} 
public class FirstLevelFactory implements IFactory {
    public Enemy createEnemy() {
       return RandomEnemy();
    }
    public Background createBackground() {
       return RandomEnemy();
    }  
    private FirstLevelEnemy RandomEnemy() {
       //lam gi do de random cac kieu
    }
    private FirstLevelBackground RandomBackground() {
       //lam gi do de random cac kieu
    }
}
public class SecondLevelFactory implements IFactory {
    public Enemy createEnemy() {
       return RandomEnemy();
    } 
    public Background createBackground() {
       return RandomEnemy();
    }  
    private SecondLevelEnemy RandomEnemy() {
       //lam gi do de random cac kieu
    }
    private SecondLevelBackground RandomBackground() {
       //lam gi do de random cac kieu
    }
}
Và cuối cùng là thêm 1 lớp factory bên ngoài IFactory được nữa. Ví dụ:
public class MotherOfFactory {
     public static IFactory createFactory(int level) {
         switch (level) {
             case 1: return FirstLevelFactory(); break;  
             case 2: return SecondLevelFactory(); break;  
             //and so on 
         }
     }
}
Bây giờ giả sử bạn ở level 1, thì có thể khởi tạo enemy bằng cách.
Enemy enemy = MotherOfFactory.createFactory(1).createEnemy();
Nếu bây giờ, bạn muốn lấy background, thì chỉ cần:
Background background = MotherOfFactory.createFactory(1).createBackground();
Lúc này Background và Enemy của bạn sẽ rất hợp lý, vì bạn đã định nghĩa nó ngay trong hiện thực của IFactory rồi.
Vậy thôi, quá đơn giản phải không?
Share:

Factory Method Pattern

Bao nhiêu bạn đã từng chơi game phi thuyền bắn súng? Giơ tay lên đi đừng ngại ngần. Có bạn nào chưa chơi game phi thuyền bắn súng không? Nếu chưa chơi thì nói thật, tuổi thơ bạn bất hạnh ghê gớm. Vì sao? vì trò chơi này là cả một tuổi thơ dữ dội, nó là cả 1 tinh hoa của nghệ thuật bắn súng trong đó. Vậy nên, chưa chơi thì hãy đi chơi đi, chơi ngay đi, vì trong bài này, mình sẽ lấy game đó làm ví dụ đấy. =))
Okie, trờ lại với bài này. Nếu như các bạn chơi game phi thuyền bắn súng thì các bạn sẽ thấy, trò chơi có các mảnh thiên thạch to nhỏ khác nhau, xuất hiện liên tục, nhiệm vụ của bạn là lo bắn phá hết tất cả cái đống đó. Vậy để generate ra được cái đống thiên thạch đó thì như nào. I zì, bạn dùng strategy pattern như trong đã học ở bài này rồi thiết kế ra thôi là được.
Giả sử bạn có thiên thạch to, thiên thạch nhỏ, thiên thạch vừa. Mỗi thiên thạch có tốc độ bay khác nhau, có lượng máu khác nhau, thì các bạn có thể dễ cmn dàng áp dụng strategy pattern để thiết kế.
Tất nhiên, bạn có thể tạo interface cho speed và healthy, nhưng ở đây mình tối giản nó lại, vì chúng ta đang học factory chứ ko phải strategy. Okie! Nói chung bạn cứ tạm hiểu là cái chúng ta đang thiết kế mấy viên thiên thạch rất hợp lý vào logic rồi.
Tiếp! Bây giờ làm thế nào để ta tạo ra mấy viên thiên thạch này bay lung tung xì phèo như game đây? Không lẽ cứ khởi tạo từng viên thiên thạch rồi ném nó vào UI à? Nghe như cứt ấy nhỉ? Vậy nên, người ta mới tạo ra ý tưởng là: "À, mình phải tạo 1 lớp, có nhiệm vụ sản sinh ra mấy cái IEnemy. IEnemy này có thể là thiên thạch to, thiên thạch nhỏ, thiên thạch vừa. Chỉ cần ta truyền parameter vào cho nó là nó sẽ tự động trả về cho mình 1 viên thiên thạch." . Ố dè, và đây chính là Factory Pattern.
Có thể hiểu ở đây, Factory là 1 cái nhà máy dùng để sản xuất IEnemy, dựa vào tên của enemy bạn muốn tạo. Và cái thể loại này ấy, người ta gọi là Simple Factory Pattern. Nhiều người cho rằng đây ko phải là pattern, ok thôi, vì mình là thằng ko thích định nghĩa nên người ta muốn bàn gì thì bàn, quan trọng là mình hiểu nó là cái gì là được rồi.
Ok! nhìn chung có vẻ ổn đúng không? Vậy tại sao người ta lại sản sinh ra thêm cái Factory Method Pattern làm gì? Sao không xài Simple Factory Pattern cho rồi, nhìn cũng ngon mà. Hờ, vấn đề là ở đây: chả có cái game củ chuối nào mà cứ bắn hoài mấy cái thiên thạch từ đầu tới cuối cả. Bạn chơi 1 hồi, tất nhiên độ khó của game sẽ tăng dần, mà độ khó game tăng tức là enemy cũng phải tăng dần. Ví dụ bạn chơi được 100 điểm, game sẽ chuyển qua level 2, chơi được 200 điểm game sẽ chuyển qua level 3. Và khi qua level 2 , level 3, game sẽ không còn là bắn thiên thạch nữa, mà chúng ta sẽ phải bắn tàu chiến, bắn rocket chẳng hạn. Vậy để giải quyết cái này thì làm sao?
Hừm, có thể chúng ta sẽ tạo ra những cái factory mới cho từng level tương ứng. Ví dụ ta có: Factory1 để sản sinh ra enemy của level 1, Factory2 để sản inh ra enemy của level 2... Nhưng nếu có 1000 level thì sao? Có thể nhiều bạn bảo: "Làm cứt gì có game 1000 level". Ôi con xin lạy, ý con chỉ là muốn lấy ví dụ để cho thấy cái vấn đề gặp phải của chúng ta thôi. Là bây giờ chúng ta sẽ có rất nhiều factory, và để đưa ra được enemy cho người chơi bắn, chúng ta phải xử lý để tiếp tục chọn factory tương ứng với level. Như vậy thì không hay. Giờ phải nghĩ ra cách mới thôi... Làm sao nhỉ???
À, đúng rồi!!! Chúng ta sẽ tạo ra 1 cái interface tên là IFactory, và những factory của từng level sẽ là hiện thực của IFactory này. Chúng sẽ có chung cơ chế sản sinh enemy là createEnemy, nhưng sẽ sản sinh ra những nhóm enemy khác nhau. Đấy, bạn bây giờ đã sử dụng Factory Method Pattern rồi đấy.
Factory Method Pattern: định nghĩa ra 1 cái interface cho phép ta khởi tạo 1 đối tượng, NHƯNG! Nó sẽ để cho lớp con của nó quyết định việc tạo ra đối tượng cụ thể. Factory Method Pattern nhường lại quyền khởi tạo cho lớp con của nó
Vậy cấu trúc cơ bản của 1 Factory Method Pattern nó là như nào? Đầu tiên ta sẽ có 1 cái interface product, và những hiện thực của nó.
Sau đó ta có 1 cái interface của Factory và những hiện thực con của nó.
Vậy đấy, Factory Method Pattern là vậy. Okie, chúng ta sẽ thử code 1 cách ngắn gọn xem nó hoạt động như nào nhá.
Đầu tiên là interface Enemy và 2 hiện thực con ở 2 level khác nhau. Chúng ta sẽ phân tầng nó để nhìn code có vẻ đẹp hơn.
public interface Enemy{
    public void speed();
    public void healthy();
} 
public interface FirstLevelEnemy extends Enemy{
} 
public interface SecondLevelEnemy extends Enemy{
}
public class BigMeteorite implements FirstLevelEmemy {
   public void speed() {
       //lam cai gi do
   }
    public void healthy(){
       //lam cai gi do
   }
}
public class VietNamSpaceShip implements SecondLevelEnemy {
   public void speed() {
       //lam cai gi do
   }
    public void healthy(){
       //lam cai gi do
   }
}
Tiếp đến ta tạo ra interface Factory và các hiện thực của nó.
public interface IFactory{
    public Enemy createEnemy();
} 
public class FirstLevelFactory implements IFactory {
    public Enemy createEnemy() {
       return RandomEnemy();
    } 
    private FirstLevelEnemy RandomEnemy() {
       //lam gi do de random cac kieu
    }
}
public class SecondLevelFactory implements IFactory {
    public Enemy createEnemy() {
       return RandomEnemy();
    } 
    private SecondLevelEnemy RandomEnemy() {
       //lam gi do de random cac kieu
    }
}
Bạn có thể bọc thêm 1 lớp factory bên ngoài IFactory được nữa. Ví dụ:
public class MotherOfFactory {
     public static IFactory createFactory(int level) {
         switch (level) {
             case 1: return FirstLevelFactory(); break;  
             case 2: return SecondLevelFactory(); break;  
             //and so on 
         }
     }
}
H bạn muốn tạo enemy của level 1 thì chỉ cần gõ vài dòng.
Enemy enemy = MotherOfFactory.createFactory(1).createEnemy();
H bạn muốn tạo enemy của level 2 thì chỉ cần gõ:
Enemy enemy = MotherOfFactory.createFactory(2).createEnemy();
Share:

Thứ Tư, 26 tháng 12, 2018

Decorator Pattern

Decorator Pattern là gì? Hừm, mình sẽ cho bạn định nghĩa dễ hiểu trước. Giả sử bạn có 1 đối tượng, và bạn muốn đối tượng đó thực hiện 1 tác vụ nào đó.
Nhưng đôi lúc, bạn lại muốn respond của bạn là 1 cái gì đó khác hơn, 1 cái gì đó được thêm mắm thêm muối vào cho mặn mà hơn, thì bạn phải làm thế nào? À, đúng rồi! Bạn sẽ thay đổi cách mà đối tượng respond bằng cách sửa code của đối tượng. Nhưng sau một thời gian, bạn lại muốn respond khác hơn, 1 chút cay nồng hơn, thì bạn phải làm sao? À, bạn lại thay đổi code của đối tượng tiếp. Và bạn chợt nhận ra, sao cái cách này nó lìn lìn thế nào ấy nhỉ? Đúng rồi đấy, vậy nên người ta mới nghĩ ra 1 cái pattern gọi là decorator patter. Cái loại này sẽ tạo ra 1 cái decorator bao bọc cái đối tượng ban đầu, sau đó nói lấy cái respond của cái đối tượng đó rắc thêm chút mắm muối rồi mới trả ngược lại cho bạn.
Như trong hình trên, cái decorator sẽ lấy respond từ object, sau đó nó sẽ rắc thêm chút muối, tẩm thêm 1 chút mắm rồi mới trả về cho bạn. Nếu như bạn muốn thêm tí cay nồng ư? Dễ thôi!
Chỉ cần thêm 1 cái decorator bao bọc bên ngoài, rồi đổ thêm tí ớt là xong. Phương pháp bao bọc thêm thắt này người ta gọi là decorator pattern.
Decorator Pattern: gắn thêm 1 vài tính năng cho 1 đối tượng 1 cách linh động. Loại pattern này cho phép ta mở rộng lớp con 1 cách linh động hơn
Arggg, sao mình ghét mấy cái khái niệm khô khan này thế nhỉ? hừm, mình sẽ bay vào ví dụ cho các bạn hiểu ngay nà. Có bạn nào đi uống trà sữa chưa? Ai nói chưa thì đi mua 1 ly đi rồi về đọc tiếp nhá. Khi bạn vào 1 quán chà sữa, bạn order sẽ gọi loại trà sữa đầu tiên. Ví dụ bạn gọi chà sữa nhà làm, hoặc có bạn thì gọi chà sữa đài loan. Sau khi bạn order loại trà sữa xong, mấy bé thu ngân sẽ kiểu gạ gẫm:
- ahihi, anh có muốn dùng thêm topping ko ạ?
- có những loại topping gì em?
- ahihi, có nhiều loại topping lắm, anh cứ đọc ở menu ạ.
- vậy lấy anh thêm chân châu chắng và hạt thủy tinh nha
- ahihi, ok anh.
- ahihi, dm em. làm nhanh anh còn về đọc design pattern=))
Đó là 1 quá trình order trà sữa điển hình. Và khi tầm tờ hóa đơn ấy, bạn sẽ đọc được dòng chữ kiểu: nhà làm + trân châu trắng. Cũng được thôi, nhưng mình là dân code đẳng cấp mà, mình muốn cái gì đó kiểu xịn sò hơn, kiểu: "Trà sữa nhà làm và trân châu trắng và hạt thủy tinh". Đấy, như thế mới gọi là bill đẳng cấp. Nhưng làm thể nào để code generate ra được dòng miêu tả xịn như thế được. Đó là lúc ta áp dụng Decorator Pattern.
Đầu tiên ta có 1 cái Interface là Milk Tea bao gồm 2 phương thức là getDescription() và getCost() chẳng hạn.
Tiếp đến ta tạo 2 cái hiện thực cho interface này là HomeMade và Taiwain.
Giờ ta tạo 1 cái interface Topping, nhưng interface này sẽ là kế thừa interface milk tea. Đồng thời tạo 2 cái hiện thực tương ứng là WhiteBubble và GlassesBubble.
Thiết kế sẽ là như thế. Nhưng phải đi vào code, bạn mới hiểu được decorator ở đây là gì. Đầu tiên là interface milk tea và 2 hiện thực homemade, taiwan.
public interface IMilkTea{
    public String getDes();
    public int getCost();
} 

public class HomeMade implements IMilkTea {
    public String getDes() {
        return "tra sua nha lam";
    }
    public int getCost() {
        return 10000;
    }
}

public class Taiwan implements IMilkTea {
    public String getDes() {
        return "tra sua dai loan";
    }
    public int getCost() {
        return 15000;
    }
}
Tiếp đến là interface topping và các hiện thực tương ứng.
public interface Topping extends IMilkTea {
} 

public class WhiteBubble implements Topping {
    private IMilkTea iMilkTea;
    public WhiteBubble (IMilkTea iMilkTea) {
         this.iMilkTea = iMilkTea;
    }
    public String getDes() {
        return iMilkTea.getDes() + " va tran chau trang";
    }
    public int getCost() {
        return iMilkTea.getCost() + 5000;
    }
}

public class GlassesBubble implements Topping {
    private IMilkTea iMilkTea;
    public GlassesBubble (IMilkTea iMilkTea) {
         this.iMilkTea = iMilkTea;
    }
    public String getDes() {
        return iMilkTea.getDes() + " va hat thuy tinh ";
    }
    public int getCost() {
        return iMilkTea.getCost() + 7000;
    }
}
Vậy là xong, bây giờ giả sử khách hàng muốn mua 1 ly trà sữa nhà làm, ta sẽ có đối tượng trà sữa nhà làm và in ra hóa đơn.
HomeMade homeMade = new HomeMade();
System.out.println(homeMade.getDes());
System.out.println(homeMade.getCost());
Nếu khách hàng muốn có topping trân châu trắng, có luôn cho khách.
WhiteBubble whiteBubble = new WhiteBubble(new HomeMade());
System.out.println(whiteBubble.getDes());
System.out.println(whiteBubble.getCost());
Khách đổi ý muốn thêm cả hạt thủy tinh nữa, chơi luôn cho khách.
GlassesBubble glassesBubble = new GlassesBubble (new WhiteBubble(new HomeMade()));
System.out.println(glassesBubble.getDes());
System.out.println(glassesBubble.getCost());
Vậy thôi. Đó chính là Decorator Pattern. Muốn thêm mắm thêm muối gì thì cứ thêm, ko phải sợ. Hê hê
Share:

Thứ Ba, 25 tháng 12, 2018

Observer Pattern

Với loại Pattern này, mình sẽ đưa ra tình huống trước. Ví dụ bạn đang làm 1 cái web dự báo thời tiết chẳng hạn. Trang web của bạn bắt buộc phải có thông tin thời tiết rồi đúng ko? Vậy là ta có 1 đối tượng gọi là "Weather". Đối tượng Weather này có 1 biến tên là "Temperature", giá trị biến này sẽ có sự thay đổi theo thòi gian. Ví dụ đêm lạnh ngày nóng chẳng hạn. Okie! Trang web của bạn để trực quan hơn thì nên có 1 cái biểu đồ để thể hiện nhiệt độ. Vậy nên ta có 1 đối tượng mới là "Chart". Nhiệm vụ của "Chart" là mỗi khi nhiệt độ thay đổi thì "Chart" sẽ vẽ lên 1 điểm mới trên biểu đồ.
Vậy để giải quyết bài toán này thì ta làm thế nào? Rất đơn giản, từ đối tượng "Chart", ta sẽ liên tục cập nhật "Temperature" của "Weather". Nếu như Temperature thay đổi, thì Chart sẽ thực hiện việc vẽ lên 1 điểm ở biểu đồ.
Okie! Nhìn có vẻ ổn đúng không? Nhưng sau một hồi ngẫm nghĩ, bạn lại muốn có thêm 1 cái bảng thống kê ghi lại những giá trị và thời gian thay đổi của Temperature. Vậy là bạn lại tạo ra 1 đối tượng "Table", và cũng làm y như "Chart" là cập nhật giá trị "Temperature".
Rồi bạn lại nhận ra, nên có thêm 1 hình animation thể hiện mức độ nóng hay lạnh ở thời điểm hiện tại, và bạn lại tạo 1 đối tượng "Animation". Đối tượng này bạn làm y hệt 2 đối tượng "Table" và "Chart". Cứ sau mỗi 1 phút, 3 đối tượng "Table", "Chart" và "Animation" lại gửi request cho "Weather" để cập nhật. Lúc này, bạn đã thấy có gì đó sai sai chạm môi bạn rồi đấy.
 Nhưng nhu cầu thông tin về thời tiết không chỉ có nhiệt độ, nó còn có về độ ẩm, về gió, về lượng mưa.... Thế nên, bạn lại thêm vào những biến "Wind", "Rain".... cho "Weather". Và tất nhiên, những đối tượng "Chart", "Table", "Animation" cũng nên cập nhật sư thay đổi của những thông số mới này. Và chính ngay lúc này đây, chi phí cho việc cập nhật giá trị quá lớn đã làm cho chương trình của bạn bị chậm đi. Nhưng chưa dừng lại ở đó, nhiều khách hàng còn muốn có thêm những tính năng khác như bản đồ thời tiết, lời khuyên thời tiết... Và vấn đề bạn chắc chắn sẽ gặp phải nếu cứ tiếp tục design theo cách này là tài nguyên của web bạn rất lớn và có thể bị crash. Và chính lúc này đây, bạn phải cần dùng đến observer pattern.
 Đơn giản là từ cách suy nghĩ "Pull", bạn phải suy nghĩ theo hướng "Push". Tức là, mỗi lần Temperature thay đổi giá trị, Weather sẽ tự động Push thông báo cho Chart , Table, Animation. Và theo kiểu suy nghĩ thiết kế này, chúng ta sẽ ko phải tốn tài nguyên cho việc liên tục gửi request để cập nhật nữa. Vậy để Weather biết phải thông báo cho ai khi Temperature thay đổi, thì những đối tượng như Chart, Table và Animation cần phải đăng kí vào Weather để nó còn biết mà la làng lên chứ. Đúng ko?
 Vậy, định nghĩa của observer Pattern là gì?
Observer Pattern: định nghĩa mối quạn hệ one-to-many giữa nhứng đối tượng với nhau. Sao cho, mỗi khi 1 đối tượng thay đổi, thì những đối tượng quan hệ với nó sẽ được thông báo và cập nhật tự động
Okie! Vậy thì chúng ta thực hiện Observer pattern như lào? Khái niệm thì có rồi đấy, nhưng mần như lào mới quan trọng. Trên mạng h có rất nhiều mô hình để hiện thực hóa cái loại pattern này, và mình chỉ lấy 1 cái ra để xài thôi. Nó sẽ được mần như lày:
Đầu tiên, bạn tạo 1 interface Observable , đây là cái interface cho những đối tượng như Weather trong ví dụ ấy. Interface này sẽ gồm các hàm chính như : add, remove và notify. Tiếp theo, bạn tạo 1 interface Observer cho những đối tượng cần được cập nhập ( như là Chart, Table, Animation). Interface này sẽ có hàm update.
Bây giờ, ta tạo 2 hiện thực cho 2 interface này. Mình sẽ tạo hiện thực Weather và Chart. Trong đó, Weather mình sẽ cho thêm 1 hàm là getTemperature(). Hàm này sẽ giúp cho Chart có thể lấy được thông tin mỗi khi cần dùng để update.
Vậy thôi, đó chính là mô hình dùng để hiện thực hóa Observer Pattern. Bây giờ, ta chỉ cần đăng kí Chart vào trong Weather bằng cách chạy hàm add(). Sau đó, mỗi lần temperature mà đổi, thì Weather sẽ chạy hàm notify(), hàm này sẽ chạy hàm update() của Chart. Vậy là xong, rất i zì.
Và giờ mình sẽ thêm tí code cho sinh động, cho mấy bro nghiền code có cái mà đọc nhá. Đầu tiên là tạo 2 cái interface cho observable và observer.
public interface IObservable{
    public void add(IObserver o);
    public void remove(IObserver o);
    public void notify();

}

public interface IObserver {
    public void update();
}
Sau đó mình tạo 2 hiện thực cho 2 cái interface này, đó là Weather và Chart.
public class Weather{
    private List listObservers = new ArrayList();
    private int temperature;
    public void add(IObserver o) {
         this.listObservers.add(o);
    }
    public void remove(IObserver o) {
         this.listObservers.remove(o);
    }
    public void notify() {
         for(IObserver o : listOBservers) {
              o.update();
         }
    }
    public int getTemperature() {
         return this.temperature;
    }
}

public class Chart{
    private Weather weather;
    public Chart(Weather weather){
        this.weather = weather;
    }
    public void update() {
         System.out.println("them 1 diem tren bieu do voi gia tri la: " + this.weather.getTemperature());
    }
}
Vậy là xong, h chỉ cần khởi tạo 2 đối tượng Weather và Chart là tắt máy đi ngủ được rồi.
Weather weather = new Weather();
Chart chart = new Chart(weather);
weather.add(chart);
Share:

Strategy Pattern

Nếu ai đó hỏi tui, pattern nào là dễ nhất! tui bợp ngay câu trả lời là : Strategy Pattern. Nếu người ta lại bắt tui miêu tả strategy pattern trong 1 câu ngắn gọn thì tui độp ngay cho 1 câu trả lời: đừng suy nghĩ theo kiểu kế thừa, mà hãy suy nghĩ theo kiểu hiện thực. Vậy, định nghĩa ( 1 cách máy móc và khó hiểu) của strategy pattern là gì?
Strategy pattern: định nghĩa một danh sách các thuật toán và đóng gói chúng để giúp chúng có thể thay đổi được. Pattern loại này giúp cho các thuật toán được định nghĩa sẳn có thể được thay đổi bởi người sử dụng chúng.
Oài, quăng ba cái định nghĩa đó đi. Đọc vào chỉ rối não chứ chả hiểu gì đâu. Nên theo chân mình, để mình dẫn bạn vào thế giới của những ví dụ.
 Giả sử, bạn có 2 con vịt: 1 con là vịt nhà, 1 con là vịt rừng. Cả 2 con này đều kêu "quạc quạc", đều có thể bay, và đều có thể nhìn thấy được. Và ta có bản thiết kế sau:

Đúng như những gì ta được học về kế thừa rồi đúng ko? Nhưng sẽ chẳng có gì to tát nếu bỗng nhưng trên trời rơi xuống thêm 1 con vịt cao su. Con vịt cao su này thì lại không biết bay, vậy là ta phải thiết kế lại.

Nhìn có vẻ ổn rồi nhể? Nhưng chưa đâu anh bạn, khách hàng kêu là: "ê chú, tui mới đặt thêm 2 loại vịt nữa. 1 con là vịt trời, 1 con là vịt núi, 2 con này nó cũng bay được bình thường, nhưng mà nó bay khác với con vịt nhà với vịt rừng chú ạ! chú coi coi sao sửa lại dùm tui gấp nhá!". Và lúc đó bạn kiểu: "ơ, goắt đờ hợi? Lại sửa à?". Vừa mới mở bản thiết kế lên để chuẩn bị chèn vào 2 con vịt chó đẻ thì ông khách hàng lại hớn hở chạy vào:
- Ê chú ơi, bên kia người ta lại sắp giao thêm vịt xi măng nữa chú ạ. Vịt xi măng thì không biết bay và cũng chả biết kêu nhá.
- Cút!!!!
Đấy, bạn thấy đấy. Lúc đầu kế thừa có vẻ là hay. Nhưng về lâu về dài thì nó lại có vấn đề. Khi gặp nhiều thay đổi thì cái cây kế thừa của bạn càng ngày càng mở rộng. Và khi bạn mở rộng quá dài thì nhận ra là 2 con vịt ở 2 nhánh cách xa nhau lại có 1 đặc điểm chung và lúc này bạn phải ngậm ngùi để cho code của bạn bị lặp lại. Và kết quả là code của bạn nhìn như cứt ấy. (Nói thẳng ra là vậy! =)) )
Vậy để giải quyết vấn đề này thì ta sẽ dùng Strategy pattern.
Ban đầu, ta có 1 con vịt. 1 con vịt thì biết bay, và nó biết kêu. Okie. Ta sẽ tạo 1 con vịt như thế, NHƯNG! ta sẽ thiết kế hành động bay và kêu này là những interface. Nghĩa là sao? nghĩa là con vịt của chúng ta có hành động bay và kêu đấy, nhưng nó chỉ là interface mà thôi, nó không có làm được gì nêu như ta chưa định nghĩa cho nó.

Tiếp đến, ta bắt đầu định nghĩa cho những interface này. Ta có con vịt nhà và vịt rừng bay kiểu nhẹ nhàng. Còn con vịt trời và vịt núi bay kiểu mạnh mẽ. Con vịt cao su và vịt xi măng thì không biết bay. Tức là ta có 3 hiện thực cho interface bay.

Tương tự với tiếng kêu của vịt cũng thế. Ta có 2 hiện thực là kêu và không kêu.

Và bây giờ để tạo ra con vịt nhà thì ta chỉ cần truyền vào cho đối tượng "DUCK" 2 hiện thực là "Smooth fly" và "Normal quack". Muốn tạo vịt xi măng thì truyền vào "DUCK" 2 hiện thực là "No fly" , "No quack". Và đây chính là strategy pattern.
 Bây giờ thử code vài dòng xem nó như thế nào xem nào?
 Đầu tiên ta tạo 2 cái interface bay và kêu.
public interface IFly {
    public void fly();
}

public interface IQuack {
    public void quack();
}
 Sau đó ta hiện thực những interface này. Đầu tiên là những lớp bay.
public class NoFly implements IFly{
    public void fly(){
         // khong lam gi ca
    }
}

public class SmoothFly implements IFly{
    public void fly(){
         Systems.out.println("bay nhe nhang!");
    }
}

public class StrongFly implements IFly{
    public void fly(){
         Systems.out.println("bay manh me!");
    }
}
 Sau đó là những lớp kêu.
public class NoQuack implements IQuack{
    public void quack(){
         // khong lam gi ca
    }
}

public class NormalQuack implements IQuack{
    public void quack(){
         Systems.out.println("Quac quac!");
    }
}
 Và giờ ta có thể tạo đối tượng vịt dựa trên 2 interface IFly và IQuack
public class Duck {
    private IFly flyBeha;
    private IQuack quackBeha;
    public Duck (IFly flyBeha, IQuack quackBeha){
        this.flyBeha = flyBeha;
        this.quackBeha = quackBeha;    
    }
    public void quack(){
         this.quackBeha.quack();
    }

    public void fly(){
         this.flyBeha.fly();
    }
}
 Đối tượng đã có hết rồi, h muốn tạo con vịt nào là có con vịt nấy.
Duck CityDuck = new Duck(new SmoothFly(), new NormalQuack());
Duck RubberDuck = new Duck(new NoFly(), new NormalQuack());
Duck CementDuck = new Duck(new NoFly(), new NoQuack());
 Strategy pattern giúp chúng ta thiết kế 1 cách tùy biến hơn, linh động hơn và giúp ta làm chủ hơn việc khởi tạo đối tượng. Với phương pháp này thì cái cây kế thừa của chung ta sẽ bị dẹp qua 1 bên và không còn phải lo lằng nhiều nếu như có thêm 1 con vịt chó chết nào được thêm vào nữa. Giả dụ chúng ta có thêm con vịt điên chẳng hạn, nó không biết kêu mà lại biết bơi. Thì cũng chỉ cần truyền vào đối tượng "DUCK" những hiện thực tương ứng thôi. Và từ đây thì cũng có thể thấy được là, tính kế thừa cũng chả có quá đỉnh cao như chúng ta vẫn được học, quan trọng là kế thừa lúc nào mới là hiệu quả.
Share:
Được tạo bởi Blogger.