JavaRush /Blog Java /Random-VI /Các mẫu thiết kế trong Java
Viacheslav
Mức độ

Các mẫu thiết kế trong Java

Xuất bản trong nhóm
Các mẫu hoặc mẫu thiết kế là một phần thường bị bỏ qua trong công việc của nhà phát triển, khiến mã khó duy trì và thích ứng với các yêu cầu mới. Tôi khuyên bạn nên xem nó là gì và nó được sử dụng như thế nào trong JDK. Đương nhiên, tất cả các khuôn mẫu cơ bản ở dạng này hay dạng khác đều đã tồn tại xung quanh chúng ta từ rất lâu. Hãy cùng xem chúng trong bài đánh giá này.
Các mẫu thiết kế trong Java - 1
Nội dung:

Mẫu

Một trong những yêu cầu phổ biến nhất khi tuyển dụng là “Kiến thức về các mẫu”. Trước hết, cần trả lời một câu hỏi đơn giản - "Mẫu thiết kế là gì?" Mẫu được dịch từ tiếng Anh là “mẫu”. Đó là, đây là một khuôn mẫu nhất định mà theo đó chúng ta làm điều gì đó. Điều này cũng đúng trong lập trình. Có một số phương pháp và cách tiếp cận tốt nhất đã được thiết lập để giải quyết các vấn đề chung. Mỗi lập trình viên đều là một kiến ​​trúc sư. Ngay cả khi bạn chỉ tạo một vài lớp hoặc thậm chí một lớp, điều đó phụ thuộc vào bạn mã có thể tồn tại trong bao lâu dưới các yêu cầu thay đổi, mức độ thuận tiện khi người khác sử dụng. Và đây là lúc kiến ​​thức về các mẫu sẽ giúp ích, bởi vì... Điều này sẽ cho phép bạn nhanh chóng hiểu cách viết mã tốt nhất mà không cần viết lại. Như bạn đã biết, lập trình viên là những người lười biếng và viết tốt một cái gì đó ngay lập tức thì dễ hơn là làm lại nhiều lần). Các mẫu cũng có thể có vẻ giống với các thuật toán. Nhưng họ có một sự khác biệt. Thuật toán bao gồm các bước cụ thể mô tả các hành động cần thiết. Các mẫu chỉ mô tả cách tiếp cận mà không mô tả các bước thực hiện. Các mẫu đều khác nhau, bởi vì... giải quyết các vấn đề khác nhau. Thông thường các loại sau đây được phân biệt:
  • sáng tạo

    Những mẫu này giải quyết vấn đề làm cho việc tạo đối tượng trở nên linh hoạt

  • Cấu trúc

    Những mẫu này giải quyết vấn đề xây dựng kết nối giữa các đối tượng một cách hiệu quả

  • hành vi

    Những mẫu này giải quyết vấn đề tương tác hiệu quả giữa các đối tượng

Để xem xét các ví dụ, tôi khuyên bạn nên sử dụng trình biên dịch mã trực tuyến repl.it.
Các mẫu thiết kế trong Java - 2

Các mẫu sáng tạo

Hãy bắt đầu từ đầu vòng đời của các đối tượng - với việc tạo ra các đối tượng. Các mẫu sáng tạo giúp tạo đối tượng thuận tiện hơn và mang lại sự linh hoạt trong quá trình này. Một trong những nổi tiếng nhất là " Người xây dựng ". Mẫu này cho phép bạn tạo các đối tượng phức tạp theo từng bước. Trong Java, ví dụ nổi tiếng nhất là StringBuilder:
class Main {
  public static void main(String[] args) {
    StringBuilder builder = new StringBuilder();
    builder.append("Hello");
    builder.append(',');
    builder.append("World!");
    System.out.println(builder.toString());
  }
}
Một cách tiếp cận phổ biến khác để tạo một đối tượng là chuyển việc tạo đó sang một phương thức riêng biệt. Phương thức này trở thành một nhà máy sản xuất đối tượng. Đó là lý do tại sao mẫu này được gọi là " Phương pháp xuất xưởng ". Ví dụ, trong Java, tác dụng của nó có thể được nhìn thấy trong lớp java.util.Calendar. Bản thân lớp này Calendarlà trừu tượng và để tạo ra nó, phương thức này được sử dụng getInstance:
import java.util.*;
class Main {
  public static void main(String[] args) {
    Calendar calendar = Calendar.getInstance();
    System.out.println(calendar.getTime());
    System.out.println(calendar.getClass().getCanonicalName());
  }
}
Điều này thường là do logic đằng sau việc tạo đối tượng có thể phức tạp. Ví dụ, trong trường hợp trên, chúng ta truy cập vào lớp cơ sở Calendarvà lớp được tạo ra GregorianCalendar. Nếu nhìn vào hàm tạo, chúng ta có thể thấy rằng các cách triển khai khác nhau được tạo tùy thuộc vào các điều kiện Calendar. Nhưng đôi khi một phương pháp sản xuất là không đủ. Đôi khi bạn cần tạo các đối tượng khác nhau để chúng khớp với nhau. Một mẫu khác sẽ giúp chúng ta thực hiện điều này - “ Nhà máy trừu tượng ”. Và sau đó chúng ta cần tạo ra các nhà máy khác nhau ở một nơi. Đồng thời, ưu điểm là chi tiết triển khai không quan trọng đối với chúng tôi, tức là. việc chúng tôi nhận được nhà máy cụ thể nào không quan trọng. Điều chính là nó tạo ra sự triển khai đúng đắn. Siêu ví dụ:
Các mẫu thiết kế trong Java - 3
Tức là tùy vào môi trường (hệ điều hành) mà chúng ta sẽ có một Factory nhất định sẽ tạo ra các phần tử tương thích. Để thay thế cho cách tiếp cận sáng tạo thông qua người khác, chúng ta có thể sử dụng mẫu " Prototype ". Bản chất của nó rất đơn giản - các đối tượng mới được tạo ra theo hình ảnh và sự giống với các đối tượng đã có, tức là. theo nguyên mẫu của họ. Mọi người đều đã gặp mẫu này trong Java - đây là cách sử dụng giao diện java.lang.Cloneable:
class Main {
  public static void main(String[] args) {
    class CloneObject implements Cloneable {
      @Override
      protected Object clone() throws CloneNotSupportedException {
        return new CloneObject();
      }
    }
    CloneObject obj = new CloneObject();
    try {
      CloneObject pattern = (CloneObject) obj.clone();
    } catch (CloneNotSupportedException e) {
      //Do something
    }
  }
}
Như bạn có thể thấy, người gọi không biết cách clone. Nghĩa là, việc tạo ra một đối tượng dựa trên nguyên mẫu là trách nhiệm của chính đối tượng đó. Điều này hữu ích vì nó không ràng buộc người dùng với việc triển khai đối tượng mẫu. Chà, mẫu cuối cùng trong danh sách này là mẫu “Singleton”. Mục đích của nó rất đơn giản - cung cấp một phiên bản duy nhất của một đối tượng cho toàn bộ ứng dụng. Mẫu này rất thú vị vì nó thường hiển thị các vấn đề đa luồng. Để có cái nhìn sâu hơn, hãy xem các bài viết sau:
Các mẫu thiết kế trong Java - 4

Mẫu kết cấu

Với việc tạo ra các đối tượng, nó trở nên rõ ràng hơn. Bây giờ là lúc để xem xét các mô hình cấu trúc. Mục tiêu của họ là xây dựng hệ thống phân cấp lớp dễ hỗ trợ và các mối quan hệ của chúng. Một trong những mẫu đầu tiên và được nhiều người biết đến là “ Phó ” (Proxy). Proxy có giao diện giống như đối tượng thực, vì vậy sẽ không có gì khác biệt khi máy khách làm việc thông qua proxy hoặc trực tiếp. Ví dụ đơn giản nhất là java.lang.reflect.Proxy :
import java.util.*;
import java.lang.reflect.*;
class Main {
  public static void main(String[] arguments) {
    final Map<String, String> original = new HashMap<>();
    InvocationHandler proxy = (obj, method, args) -> {
      System.out.println("Invoked: " + method.getName());
      return method.invoke(original, args);
    };
    Map<String, String> proxyInstance = (Map) Proxy.newProxyInstance(
        original.getClass().getClassLoader(),
        original.getClass().getInterfaces(),
        proxy);
    proxyInstance.put("key", "value");
    System.out.println(proxyInstance.get("key"));
  }
}
Như bạn có thể thấy, trong ví dụ này chúng ta có bản gốc - đây là bản gốc HashMaptriển khai giao diện Map. Tiếp theo, chúng tôi tạo một proxy thay thế proxy gốc HashMapcho phần máy khách, phần này gọi putvà các phương thức get, thêm logic của riêng chúng tôi trong suốt cuộc gọi. Như chúng ta có thể thấy, sự tương tác trong mẫu xảy ra thông qua các giao diện. Nhưng đôi khi sự thay thế là không đủ. Và sau đó có thể sử dụng mẫu " Decorator ". Trình trang trí còn được gọi là trình bao bọc hoặc trình bao bọc. Proxy và trang trí rất giống nhau, nhưng nếu nhìn vào ví dụ, bạn sẽ thấy sự khác biệt:
import java.util.*;
class Main {
  public static void main(String[] arguments) {
    List<String> list = new ArrayList<>();
    List<String> decorated = Collections.checkedList(list, String.class);
    decorated.add("2");
    list.add("3");
    System.out.println(decorated);
  }
}
Không giống như proxy, trình trang trí tự bao quanh nội dung nào đó được chuyển làm đầu vào. Một proxy có thể vừa chấp nhận những gì cần được ủy quyền, vừa quản lý vòng đời của đối tượng được ủy quyền (ví dụ: tạo một đối tượng được ủy quyền). Có một mẫu thú vị khác - “ Adaptor ”. Nó tương tự như một trình trang trí - trình trang trí lấy một đối tượng làm đầu vào và trả về một trình bao bọc trên đối tượng này. Sự khác biệt là mục tiêu không phải là thay đổi chức năng mà là điều chỉnh giao diện này sang giao diện khác. Java có một ví dụ rất rõ ràng về điều này:
import java.util.*;
class Main {
  public static void main(String[] arguments) {
    String[] array = {"One", "Two", "Three"};
    List<String> strings = Arrays.asList(array);
    strings.set(0, "1");
    System.out.println(Arrays.toString(array));
  }
}
Ở đầu vào chúng ta có một mảng. Tiếp theo, chúng ta tạo một bộ chuyển đổi đưa mảng vào giao diện List. Khi làm việc với nó, chúng ta thực sự đang làm việc với một mảng. Do đó, việc thêm các phần tử sẽ không hiệu quả, bởi vì... Mảng ban đầu không thể thay đổi. Và trong trường hợp này chúng ta sẽ nhận được UnsupportedOperationException. Cách tiếp cận thú vị tiếp theo để phát triển cấu trúc lớp là mẫu Composite . Điều thú vị là một tập hợp các phần tử nhất định sử dụng một giao diện được sắp xếp theo một hệ thống phân cấp giống như cây nhất định. Bằng cách gọi một phương thức trên phần tử cha, chúng ta nhận được lệnh gọi phương thức này trên tất cả các phần tử con cần thiết. Một ví dụ điển hình của mẫu này là UI (có thể là java.awt hoặc JSF):
import java.awt.*;
class Main {
  public static void main(String[] arguments) {
    Container container = new Container();
    Component component = new java.awt.Component(){};
    System.out.println(component.getComponentOrientation().isLeftToRight());
    container.add(component);
    container.applyComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
    System.out.println(component.getComponentOrientation().isLeftToRight());
  }
}
Như chúng ta có thể thấy, chúng ta đã thêm một thành phần vào vùng chứa. Và sau đó chúng tôi yêu cầu vùng chứa áp dụng hướng mới của các thành phần. Và vùng chứa, biết nó bao gồm những thành phần nào, đã ủy quyền thực thi lệnh này cho tất cả các thành phần con. Một mẫu thú vị khác là mẫu “ Cây cầu ”. Nó được gọi như vậy vì nó mô tả sự kết nối hoặc cầu nối giữa hai hệ thống phân cấp lớp khác nhau. Một trong những hệ thống phân cấp này được coi là trừu tượng và hệ thống kia là triển khai. Điều này được làm nổi bật vì bản thân sự trừu tượng hóa không thực hiện các hành động mà ủy quyền việc thực thi này cho việc triển khai. Mẫu này thường được sử dụng khi có các lớp "điều khiển" và một số loại lớp "nền tảng" (ví dụ: Windows, Linux, v.v.). Với cách tiếp cận này, một trong các hệ thống phân cấp này (trừu tượng hóa) sẽ nhận được tham chiếu đến các đối tượng của hệ thống phân cấp khác (triển khai) và sẽ ủy thác công việc chính cho chúng. Bởi vì tất cả các triển khai sẽ tuân theo một giao diện chung nên chúng có thể được thay thế cho nhau trong phần trừu tượng. Trong Java, một ví dụ rõ ràng về điều này là java.awt:
Các mẫu thiết kế trong Java - 5
Để biết thêm thông tin, hãy xem bài viết " Các mẫu trong Java AWT ". Trong số các mẫu cấu trúc, tôi cũng muốn lưu ý đến mẫu “ Mặt tiền ”. Bản chất của nó là che giấu sự phức tạp của việc sử dụng các thư viện/khuôn khổ đằng sau API này bằng một giao diện tiện lợi và ngắn gọn. Ví dụ: bạn có thể sử dụng JSF hoặc EntityManager từ JPA làm ví dụ. Ngoài ra còn có một mẫu khác gọi là " Flyweight ". Bản chất của nó là nếu các đối tượng khác nhau có cùng một trạng thái, thì nó có thể được khái quát hóa và lưu trữ không phải ở mỗi đối tượng mà ở một nơi. Và khi đó mỗi đối tượng sẽ có thể tham chiếu đến một phần chung, điều này sẽ giảm chi phí bộ nhớ cho việc lưu trữ. Mẫu này thường liên quan đến việc lưu trước vào bộ nhớ đệm hoặc duy trì một nhóm đối tượng. Điều thú vị là chúng ta cũng biết mô hình này ngay từ đầu:
Các mẫu thiết kế trong Java - 6
Bằng cách tương tự, một nhóm các chuỗi có thể được đưa vào đây. Bạn có thể đọc bài viết về chủ đề này: " Mẫu thiết kế Flyweight ".
Các mẫu thiết kế trong Java - 7

Mẫu hành vi

Vì vậy, chúng tôi đã tìm ra cách có thể tạo các đối tượng và cách tổ chức các kết nối giữa các lớp. Điều thú vị nhất còn lại là mang lại sự linh hoạt trong việc thay đổi hành vi của các đối tượng. Và các mô hình hành vi sẽ giúp chúng ta điều này. Một trong những mẫu được nhắc đến thường xuyên nhất là mẫu “ Chiến lược ”. Đây là nơi bắt đầu nghiên cứu về các mẫu trong cuốn sách “ Head First. Design Patterns ” bắt đầu. Bằng cách sử dụng mẫu “Chiến lược”, chúng ta có thể lưu trữ bên trong một đối tượng cách chúng ta sẽ thực hiện hành động, tức là. đối tượng bên trong lưu trữ một chiến lược có thể được thay đổi trong quá trình thực thi mã. Đây là mẫu chúng ta thường sử dụng khi sử dụng bộ so sánh:
import java.util.*;
class Main {
  public static void main(String[] args) {
    List<String> data = Arrays.asList("Moscow", "Paris", "NYC");
    Comparator<String> comparator = Comparator.comparingInt(String::length);
    Set dataSet = new TreeSet(comparator);
    dataSet.addAll(data);
    System.out.println("Dataset : " + dataSet);
  }
}
Trước chúng tôi - TreeSet. Nó có hành vi TreeSetduy trì thứ tự của các phần tử, tức là sắp xếp chúng (vì nó là một SortedSet). Hành vi này có một chiến lược mặc định mà chúng ta thấy trong JavaDoc: sắp xếp theo "thứ tự tự nhiên" (đối với chuỗi, đây là thứ tự từ điển). Điều này xảy ra nếu bạn sử dụng hàm tạo không tham số. Nhưng nếu muốn thay đổi chiến lược, chúng ta có thể vượt qua Comparator. Trong ví dụ này, chúng ta có thể tạo tập hợp của mình dưới dạng new TreeSet(comparator), sau đó thứ tự lưu trữ các phần tử (chiến lược lưu trữ) sẽ thay đổi thành tập hợp được chỉ định trong bộ so sánh. Điều thú vị là có một mẫu gần như giống nhau được gọi là " Trạng thái ". Mẫu “Trạng thái” nói rằng nếu chúng ta có một số hành vi trong đối tượng chính phụ thuộc vào trạng thái của đối tượng này, thì chúng ta có thể mô tả chính trạng thái đó như một đối tượng và thay đổi đối tượng trạng thái. Và ủy quyền các cuộc gọi từ đối tượng chính đến trạng thái. Một mẫu khác mà chúng tôi biết đến khi nghiên cứu những điều cơ bản nhất của ngôn ngữ Java là mẫu “ Lệnh ”. Mẫu thiết kế này gợi ý rằng các lệnh khác nhau có thể được biểu diễn dưới dạng các lớp khác nhau. Mẫu này rất giống với mẫu Chiến lược. Nhưng trong mẫu Chiến lược, chúng tôi đã xác định lại cách thực hiện một hành động cụ thể (ví dụ: sắp xếp theo TreeSet). Trong mẫu “Lệnh”, chúng tôi xác định lại hành động nào sẽ được thực hiện. Lệnh mẫu luôn ở bên chúng ta hàng ngày khi chúng ta sử dụng các luồng:
import java.util.*;
class Main {
  public static void main(String[] args) {
    Runnable command = () -> {
      System.out.println("Command action");
    };
    Thread th = new Thread(command);
    th.start();
  }
}
Như bạn có thể thấy, lệnh xác định một hành động hoặc lệnh sẽ được thực thi trong một luồng mới. Cũng cần xem xét mô hình “ Chuỗi trách nhiệm ”. Mẫu này cũng rất đơn giản. Mẫu này cho biết rằng nếu nội dung nào đó cần được xử lý thì bạn có thể thu thập các trình xử lý trong một chuỗi. Ví dụ: mẫu này thường được sử dụng trong các máy chủ web. Ở đầu vào, máy chủ có một số yêu cầu từ người dùng. Yêu cầu này sau đó sẽ đi qua chuỗi xử lý. Chuỗi trình xử lý này bao gồm các bộ lọc (ví dụ: không chấp nhận các yêu cầu từ danh sách đen các địa chỉ IP), trình xử lý xác thực (chỉ cho phép người dùng được ủy quyền), trình xử lý tiêu đề yêu cầu, trình xử lý bộ đệm, v.v. Nhưng có một ví dụ đơn giản và dễ hiểu hơn trong Java java.util.logging:
import java.util.logging.*;
class Main {
  public static void main(String[] args) {
    Logger logger = Logger.getLogger(Main.class.getName());
    ConsoleHandler consoleHandler = new ConsoleHandler(){
		@Override
            public void publish(LogRecord record) {
                System.out.println("LogRecord обработан");
            }
        };
    logger.addHandler(consoleHandler);
    logger.info("test");
  }
}
Như bạn có thể thấy, Trình xử lý được thêm vào danh sách trình xử lý nhật ký. Khi trình ghi nhật ký nhận được thông báo để xử lý, mỗi thông báo như vậy sẽ chuyển qua một chuỗi trình xử lý (từ logger.getHandlers) cho trình ghi nhật ký đó. Một mẫu khác mà chúng ta thấy hàng ngày là “ Iterator ”. Bản chất của nó là tách một tập hợp các đối tượng (tức là một lớp đại diện cho cấu trúc dữ liệu. Ví dụ: List) và truyền tải bộ sưu tập này.
import java.util.*;
class Main {
  public static void main(String[] args) {
    List<String> data = Arrays.asList("Moscow", "Paris", "NYC");
    Iterator<String> iterator = data.iterator();
    while (iterator.hasNext()) {
      System.out.println(iterator.next());
    }
  }
}
Như bạn có thể thấy, iterator không phải là một phần của bộ sưu tập mà được biểu thị bằng một lớp riêng biệt đi qua bộ sưu tập. Người dùng trình vòng lặp thậm chí có thể không biết nó đang lặp lại bộ sưu tập nào, tức là. anh ấy đang tham quan bộ sưu tập nào? Rất đáng để xem xét mô hình “ Khách truy cập ”. Mẫu khách truy cập rất giống với mẫu vòng lặp. Mẫu này giúp bạn bỏ qua cấu trúc của đối tượng và thực hiện các hành động trên các đối tượng này. Họ khác nhau hơn là về khái niệm. Trình lặp duyệt qua bộ sưu tập để máy khách sử dụng trình vòng lặp không quan tâm bộ sưu tập bên trong là gì, chỉ các phần tử trong chuỗi là quan trọng. Khách truy cập có nghĩa là có một hệ thống phân cấp hoặc cấu trúc nhất định của các đối tượng mà chúng ta truy cập. Ví dụ: chúng ta có thể sử dụng xử lý thư mục riêng và xử lý tệp riêng biệt. Java có cách triển khai sẵn có của mẫu này ở dạng java.nio.file.FileVisitor:
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.io.*;
class Main {
  public static void main(String[] args) {
    SimpleFileVisitor visitor = new SimpleFileVisitor() {
      @Override
      public FileVisitResult visitFile(Object file, BasicFileAttributes attrs) throws IOException {
        System.out.println("File:" + file.toString());
        return FileVisitResult.CONTINUE;
      }
    };
    Path pathSource = Paths.get(System.getProperty("java.io.tmpdir"));
    try {
      Files.walkFileTree(pathSource, visitor);
    } catch (AccessDeniedException e) {
      // skip
    } catch (IOException e) {
      // Do something
    }
  }
}
Đôi khi cần một số đối tượng phản ứng với những thay đổi của các đối tượng khác và khi đó mẫu “Người quan sát” sẽ giúp chúng ta . Cách thuận tiện nhất là cung cấp cơ chế đăng ký cho phép một số đối tượng giám sát và phản hồi các sự kiện xảy ra ở các đối tượng khác. Mẫu này thường được sử dụng trong nhiều Listener và Observer khác nhau để phản ứng với các sự kiện khác nhau. Một ví dụ đơn giản, chúng ta có thể nhớ lại việc triển khai mẫu này từ phiên bản đầu tiên của JDK:
import java.util.*;
class Main {
  public static void main(String[] args) {
    Observer observer = (obj, arg) -> {
      System.out.println("Arg: " + arg);
    };
    Observable target = new Observable(){
      @Override
      public void notifyObservers(Object arg) {
        setChanged();
        super.notifyObservers(arg);
      }
    };
    target.addObserver(observer);
    target.notifyObservers("Hello, World!");
  }
}
Có một mô hình hành vi hữu ích khác - “ Người hòa giải ”. Nó rất hữu ích vì trong các hệ thống phức tạp, nó giúp loại bỏ kết nối giữa các đối tượng khác nhau và ủy quyền tất cả các tương tác giữa các đối tượng cho một số đối tượng, là đối tượng trung gian. Một trong những ứng dụng nổi bật nhất của mẫu này là Spring MVC, sử dụng mẫu này. Bạn có thể đọc thêm về điều này tại đây: " Spring: Mediator Pattern ". Bạn thường có thể thấy điều tương tự trong các ví dụ java.util.Timer:
import java.util.*;
class Main {
  public static void main(String[] args) {
    Timer mediator = new Timer("Mediator");
    TimerTask command = new TimerTask() {
      @Override
      public void run() {
        System.out.println("Command pattern");
        mediator.cancel();
      }
    };
    mediator.schedule(command, 1000);
  }
}
Ví dụ này trông giống một mẫu lệnh hơn. Và bản chất của mẫu "Người hòa giải" được ẩn giấu trong việc triển khai Timer'a. Bên trong bộ đếm thời gian có một hàng đợi nhiệm vụ TaskQueue, có một luồng TimerThread. Chúng tôi, với tư cách là khách hàng của lớp này, không tương tác với chúng mà tương tác với Timerđối tượng, đối tượng này, để đáp lại lệnh gọi của chúng tôi đối với các phương thức của nó, sẽ truy cập các phương thức của các đối tượng khác mà nó là trung gian. Bên ngoài nó có vẻ rất giống với "Mặt tiền". Nhưng điểm khác biệt là khi sử dụng Facade, các thành phần không biết Facade đó tồn tại và trao đổi với nhau. Và khi sử dụng “Mediator” thì các thành phần biết và sử dụng bên trung gian chứ không liên hệ trực tiếp với nhau. Cần xem xét mẫu “ Phương thức mẫu ”, mẫu này đã rõ ràng ngay từ tên của nó. Điểm mấu chốt là mã được viết theo cách mà người dùng mã (nhà phát triển) được cung cấp một số mẫu thuật toán, các bước trong đó được phép xác định lại. Điều này cho phép người dùng mã không viết toàn bộ thuật toán mà chỉ nghĩ về cách thực hiện chính xác một hoặc một bước khác của thuật toán này. Ví dụ: Java có một lớp trừu tượng AbstractListxác định hành vi của trình vòng lặp bằng List. Tuy nhiên, bản thân iterator sử dụng các phương thức lá như: get, set, remove. Hành vi của các phương pháp này được xác định bởi nhà phát triển các phương pháp con cháu AbstractList. Do đó, trình vòng lặp trong AbstractList- là một mẫu cho thuật toán lặp trên một trang tính. Và các nhà phát triển triển khai cụ thể AbstractListsẽ thay đổi hành vi của lần lặp này bằng cách xác định hành vi của các bước cụ thể. Mẫu cuối cùng mà chúng tôi phân tích là mẫu “ Ảnh chụp nhanh ” (Momento). Bản chất của nó là bảo toàn một trạng thái nhất định của một đối tượng với khả năng khôi phục trạng thái này. Ví dụ dễ nhận biết nhất từ ​​JDK là tuần tự hóa đối tượng, tức là java.io.Serializable. Hãy xem một ví dụ:
import java.io.*;
import java.util.*;
class Main {
  public static void main(String[] args) throws IOException {
    ArrayList<String> list = new ArrayList<>();
    list.add("test");
    // Save State
    ByteArrayOutputStream stream = new ByteArrayOutputStream();
    try (ObjectOutputStream out = new ObjectOutputStream(stream)) {
      out.writeObject(list);
    }
    // Load state
    byte[] bytes = stream.toByteArray();
    InputStream inputStream = new ByteArrayInputStream(bytes);
    try (ObjectInputStream in = new ObjectInputStream(inputStream)) {
      List<String> listNew = (List<String>) in.readObject();
      System.out.println(listNew.get(0));
    } catch (ClassNotFoundException e) {
      // Do something. Can't find class fpr saved state
    }
  }
}
Các mẫu thiết kế trong Java - 8

Phần kết luận

Như chúng ta đã thấy từ bài đánh giá, có rất nhiều mẫu khác nhau. Mỗi người trong số họ giải quyết vấn đề riêng của mình. Và việc biết những mẫu này có thể giúp bạn hiểu kịp thời cách viết hệ thống của mình sao cho nó linh hoạt, có thể bảo trì và có khả năng chống lại sự thay đổi. Và cuối cùng, một số liên kết để tìm hiểu sâu hơn: #Viacheslav
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION