JavaRush /จาวาบล็อก /Random-TH /รูปแบบการออกแบบใน Java
Viacheslav
ระดับ

รูปแบบการออกแบบใน Java

เผยแพร่ในกลุ่ม
รูปแบบหรือรูปแบบการออกแบบมักถูกมองข้ามในงานของนักพัฒนา ทำให้โค้ดยากต่อการบำรุงรักษาและปรับให้เข้ากับข้อกำหนดใหม่ ฉันขอแนะนำให้คุณดูว่ามันคืออะไรและใช้ใน JDK อย่างไร โดยธรรมชาติแล้ว รูปแบบพื้นฐานทั้งหมดในรูปแบบใดรูปแบบหนึ่งนั้นอยู่รอบตัวเรามาเป็นเวลานาน เรามาดูกันดีกว่าในรีวิวนี้
รูปแบบการออกแบบใน Java - 1
เนื้อหา:

เทมเพลต

ข้อกำหนดที่พบบ่อยที่สุดประการหนึ่งในตำแหน่งงานว่างคือ “ความรู้เกี่ยวกับรูปแบบ” ก่อนอื่น คุ้มค่าที่จะตอบคำถามง่ายๆ - “Design Pattern คืออะไร” รูปแบบแปลจากภาษาอังกฤษว่า "เทมเพลต" นั่นคือนี่คือรูปแบบบางอย่างตามที่เราทำบางสิ่งบางอย่าง เช่นเดียวกับในการเขียนโปรแกรม มีแนวทางปฏิบัติที่ดีที่สุดและแนวทางที่กำหนดไว้ในการแก้ไขปัญหาทั่วไป โปรแกรมเมอร์ทุกคนเป็นสถาปนิก แม้ว่าคุณจะสร้างคลาสเพียงไม่กี่คลาสหรือคลาสเดียวก็ตาม มันขึ้นอยู่กับคุณว่าโค้ดจะอยู่รอดได้นานแค่ไหนภายใต้ข้อกำหนดที่เปลี่ยนแปลงไป และความสะดวกในการใช้งานโดยผู้อื่น และนี่คือจุดที่ความรู้เกี่ยวกับเทมเพลตจะช่วยได้ เพราะ... สิ่งนี้จะช่วยให้คุณเข้าใจได้อย่างรวดเร็วว่าจะเขียนโค้ดอย่างไรให้ดีที่สุดโดยไม่ต้องเขียนใหม่ ดังที่คุณทราบ โปรแกรมเมอร์เป็นคนเกียจคร้าน และการเขียนบางสิ่งได้ดีในทันทีง่ายกว่าการทำซ้ำหลายครั้ง) รูปแบบอาจดูเหมือนคล้ายกับอัลกอริธึม แต่พวกเขามีความแตกต่าง อัลกอริทึมประกอบด้วยขั้นตอนเฉพาะที่อธิบายการดำเนินการที่จำเป็น รูปแบบอธิบายวิธีการเท่านั้น แต่ไม่ได้อธิบายขั้นตอนการดำเนินการ รูปแบบจะต่างกันเพราะว่า... แก้ไขปัญหาต่างๆ โดยทั่วไปจะแยกประเภทต่อไปนี้:
  • กำเนิด

    รูปแบบเหล่านี้ช่วยแก้ปัญหาในการสร้างวัตถุให้มีความยืดหยุ่น

  • โครงสร้าง

    รูปแบบเหล่านี้แก้ปัญหาการสร้างการเชื่อมต่อระหว่างวัตถุได้อย่างมีประสิทธิภาพ

  • พฤติกรรม

    รูปแบบเหล่านี้แก้ปัญหาการมีปฏิสัมพันธ์ระหว่างวัตถุอย่างมีประสิทธิผล

เพื่อพิจารณาตัวอย่าง ฉันขอแนะนำให้ใช้repl.itคอมไพเลอร์ โค้ดออนไลน์
รูปแบบการออกแบบใน Java - 2

รูปแบบการสร้างสรรค์

เริ่มจากจุดเริ่มต้นของวงจรชีวิตของวัตถุ - ด้วยการสร้างวัตถุ เทมเพลตทั่วไปช่วยสร้างออบเจ็กต์ได้สะดวกยิ่งขึ้นและให้ความยืดหยุ่นในกระบวนการนี้ สิ่งที่มีชื่อเสียงที่สุดคือ " ผู้สร้าง " รูปแบบนี้ช่วยให้คุณสร้างวัตถุที่ซับซ้อนได้ทีละขั้นตอน ใน Java ตัวอย่างที่มีชื่อเสียงที่สุดคือ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());
  }
}
อีกแนวทางหนึ่งที่รู้จักกันดีในการสร้างออบเจ็กต์คือการย้ายการสร้างไปยังวิธีที่แยกจากกัน วิธีการนี้จะกลายเป็นโรงงานออบเจ็กต์ นั่นเป็นเหตุผลว่าทำไมรูปแบบนี้จึงเรียกว่า " Factory Method" java.util.Calendarตัวอย่างเช่น ใน Java เอฟเฟก ต์สามารถเห็นได้ในคลาส คลาสนั้นCalendarเป็นนามธรรม และเพื่อสร้างมันขึ้นมาจะใช้เมธอด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());
  }
}
สาเหตุนี้มักเกิดขึ้นเนื่องจากตรรกะเบื้องหลังการสร้างออบเจ็กต์อาจมีความซับซ้อน ตัวอย่างเช่น ในกรณีข้างต้น เราเข้าถึงคลาสฐานCalendarและคลาสก็ถูกสร้างGregorianCalendarขึ้น หากเราดูที่ Constructor เราจะเห็นว่ามีการสร้างการใช้งานที่แตกต่างกันขึ้นอยู่กับCalendarเงื่อนไข แต่บางครั้งวิธีการจากโรงงานวิธีเดียวก็ไม่เพียงพอ บางครั้งคุณจำเป็นต้องสร้างวัตถุต่างๆ เพื่อให้เข้ากันได้ เทมเพลตอื่นจะช่วยเราในเรื่องนี้ - " โรงงานนามธรรม " จากนั้นเราก็ต้องสร้างโรงงานต่างๆ ขึ้นมาในที่เดียว ในขณะเดียวกันข้อดีก็คือรายละเอียดการดำเนินการไม่สำคัญสำหรับเรานั่นคือ ไม่สำคัญว่าเราจะได้โรงงานแห่งใดโดยเฉพาะ สิ่งสำคัญคือการสร้างการใช้งานที่ถูกต้อง ตัวอย่างสุดยอด:
รูปแบบการออกแบบใน Java - 3
นั่นคือขึ้นอยู่กับสภาพแวดล้อม (ระบบปฏิบัติการ) เราจะได้รับโรงงานบางแห่งที่จะสร้างองค์ประกอบที่เข้ากันได้ อีกทางเลือกหนึ่งแทนแนวทางการสร้างผ่านบุคคลอื่น เราสามารถใช้รูปแบบ " Prototype " ได้ สาระสำคัญของมันนั้นเรียบง่าย - วัตถุใหม่ถูกสร้างขึ้นในภาพและความคล้ายคลึงของวัตถุที่มีอยู่แล้วเช่น ตามต้นแบบของพวกเขา ใน Java ทุกคนเคยเจอรูปแบบนี้ - นี่คือการใช้อินเทอร์เฟซ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
    }
  }
}
อย่างที่คุณเห็น ผู้โทรไม่รู้ว่าวิธีcloneการ นั่นคือการสร้างออบเจ็กต์ตามต้นแบบถือเป็นความรับผิดชอบของออบเจ็กต์นั้นเอง สิ่งนี้มีประโยชน์เนื่องจากไม่ได้ผูกผู้ใช้กับการใช้งานออบเจ็กต์เทมเพลต อันสุดท้ายในรายการนี้คือรูปแบบ “ซิงเกิลตัน” วัตถุประสงค์นั้นเรียบง่าย - เพื่อจัดเตรียมออบเจ็กต์เดียวสำหรับแอปพลิเคชันทั้งหมด รูปแบบนี้น่าสนใจเนื่องจากมักจะแสดงปัญหาแบบมัลติเธรด หากต้องการดูเชิงลึกยิ่งขึ้น โปรดดูบทความเหล่านี้:
รูปแบบการออกแบบใน Java - 4

รูปแบบโครงสร้าง

เมื่อสร้างวัตถุขึ้นมาก็ชัดเจนขึ้น ตอนนี้เป็นเวลาที่จะดูรูปแบบโครงสร้าง เป้าหมายของพวกเขาคือการสร้างลำดับชั้นของชั้นเรียนที่ง่ายต่อการสนับสนุนและความสัมพันธ์ของพวกเขา รูปแบบแรกและเป็นที่รู้จักคือ “ รอง ” (ผู้รับมอบฉันทะ) พร็อกซีมีอินเทอร์เฟซเดียวกันกับออบเจ็กต์จริง ดังนั้นจึงไม่มีความแตกต่างสำหรับไคลเอ็นต์ในการทำงานผ่านพร็อกซีหรือโดยตรง ตัวอย่างที่ง่ายที่สุดคือ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"));
  }
}
อย่างที่คุณเห็นในตัวอย่างที่เรามี ต้นฉบับ - นี่คืออันHashMapที่ใช้อินเทอร์เฟซMap. ต่อไปเราจะสร้างพรอกซีที่จะแทนที่อันเดิมHashMapสำหรับส่วนของไคลเอนต์ ซึ่งเรียกใช้เมธอดputและgetโดยเพิ่มตรรกะของเราเองระหว่างการโทร ดังที่เราเห็น การโต้ตอบในรูปแบบเกิดขึ้นผ่านอินเทอร์เฟซ แต่บางครั้งตัวสำรองยังไม่เพียงพอ จากนั้นจึง สามารถใช้รูปแบบ" มัณฑนากร " ได้ มัณฑนากรเรียกอีกอย่างว่ากระดาษห่อหรือกระดาษห่อ พร็อกซีและมัณฑนากรคล้ายกันมาก แต่ถ้าคุณดูตัวอย่าง คุณจะเห็นความแตกต่าง:
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);
  }
}
ต่างจากพรอกซี มัณฑนากรจะล้อมรอบบางสิ่งที่ส่งผ่านเป็นอินพุต พร็อกซีสามารถยอมรับสิ่งที่จำเป็นต้องมีพร็อกซีและจัดการอายุการใช้งานของอ็อบเจ็กต์พร็อกซีได้ (เช่น สร้างอ็อบเจ็กต์พร็อกซี) มีอีกรูปแบบหนึ่งที่น่าสนใจ - “ อะแดปเตอร์ ” มันคล้ายกับมัณฑนากร - มัณฑนากรใช้วัตถุหนึ่งชิ้นเป็นอินพุตและส่งกลับเสื้อคลุมเหนือวัตถุนี้ ความแตกต่างก็คือเป้าหมายไม่ใช่การเปลี่ยนฟังก์ชันการทำงาน แต่เป็นการปรับอินเทอร์เฟซหนึ่งไปยังอีกอินเทอร์เฟซหนึ่ง Java มีตัวอย่างที่ชัดเจนมากเกี่ยวกับสิ่งนี้:
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));
  }
}
ที่อินพุตเรามีอาร์เรย์ ต่อไป เราจะสร้างอะแดปเตอร์ที่นำอาร์เรย์มาสู่อินเทอร์เฟListซ เมื่อทำงานกับมัน เรากำลังทำงานกับอาร์เรย์จริงๆ ดังนั้นการเพิ่มองค์ประกอบจะไม่ทำงานเพราะ... อาร์เรย์เดิมไม่สามารถเปลี่ยนแปลงได้ และในกรณีนี้เราจะได้รับUnsupportedOperationException. แนวทางที่น่าสนใจต่อไปในการพัฒนาโครงสร้างคลาสคือรูป แบบ คอมโพสิต สิ่งที่น่าสนใจคือชุดองค์ประกอบบางชุดที่ใช้อินเทอร์เฟซเดียวถูกจัดเรียงในลำดับชั้นที่เหมือนต้นไม้ โดยการเรียกเมธอดบนอิลิเมนต์พาเรนต์ เราจึงได้รับการเรียกเมธอดนี้ในอิลิเมนต์ลูกที่จำเป็นทั้งหมด ตัวอย่างที่สำคัญของรูปแบบนี้คือ UI (ไม่ว่าจะเป็น java.awt หรือ 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());
  }
}
ดังที่เราเห็น เราได้เพิ่มส่วนประกอบลงในคอนเทนเนอร์แล้ว จากนั้นเราขอให้คอนเทนเนอร์ใช้การวางแนวใหม่ของส่วนประกอบต่างๆ และคอนเทนเนอร์เมื่อรู้ว่าส่วนประกอบใดประกอบด้วยอะไรบ้าง จึงมอบหมายการดำเนินการของคำสั่งนี้ให้กับส่วนประกอบย่อยทั้งหมด อีกรูปแบบที่น่าสนใจคือลาย “ สะพาน ” มันถูกเรียกสิ่งนี้เพราะมันอธิบายการเชื่อมต่อหรือสะพานเชื่อมระหว่างลำดับชั้นของคลาสที่แตกต่างกันสองลำดับ หนึ่งในลำดับชั้นเหล่านี้ถือเป็นนามธรรมและอีกลำดับหนึ่งถือเป็นการนำไปปฏิบัติ สิ่งนี้ถูกเน้นเนื่องจากสิ่งที่เป็นนามธรรมนั้นไม่ได้ดำเนินการใดๆ แต่มอบหมายการดำเนินการนี้ให้กับการใช้งาน รูปแบบนี้มักใช้เมื่อมีคลาส "ควบคุม" และคลาส "แพลตฟอร์ม" หลายประเภท (เช่น Windows, Linux เป็นต้น) ด้วยแนวทางนี้ หนึ่งในลำดับชั้นเหล่านี้ (นามธรรม) จะได้รับการอ้างอิงถึงออบเจ็กต์ของลำดับชั้นอื่น (การนำไปใช้) และจะมอบหมายงานหลักให้กับพวกเขา เนื่องจากการใช้งานทั้งหมดจะเป็นไปตามอินเทอร์เฟซทั่วไป จึงสามารถสับเปลี่ยนกันได้ภายในนามธรรม ใน Java ตัวอย่างที่ชัดเจนคือjava.awt:
รูปแบบการออกแบบใน Java - 5
สำหรับข้อมูลเพิ่มเติม โปรดดูบทความ " รูปแบบใน Java AWT " ในบรรดารูปแบบโครงสร้าง ผมอยากจะสังเกตรูปแบบ " Facade " ด้วย สิ่งสำคัญคือการซ่อนความซับซ้อนของการใช้ไลบรารี/เฟรมเวิร์กเบื้องหลัง API นี้ เบื้องหลังอินเทอร์เฟซที่สะดวกและกระชับ ตัวอย่างเช่น คุณสามารถใช้ JSF หรือ EntityManager จาก JPA เป็นตัวอย่างได้ นอกจากนี้ยังมีอีกรูปแบบหนึ่งที่เรียกว่า " ฟลายเวท " สาระสำคัญของมันคือหากวัตถุที่แตกต่างกันมีสถานะเดียวกันก็สามารถสรุปและจัดเก็บไม่ได้ในแต่ละวัตถุ แต่ในที่เดียว จากนั้นแต่ละออบเจ็กต์จะสามารถอ้างอิงส่วนทั่วไปได้ซึ่งจะช่วยลดต้นทุนหน่วยความจำในการจัดเก็บ รูปแบบนี้มักจะเกี่ยวข้องกับการแคชล่วงหน้าหรือการรักษากลุ่มของอ็อบเจ็กต์ ที่น่าสนใจคือ เรายังทราบรูปแบบนี้ตั้งแต่เริ่มต้น:
รูปแบบการออกแบบใน Java - 6
ในทำนองเดียวกัน สามารถรวมกลุ่มสตริงไว้ที่นี่ได้ คุณสามารถอ่านบทความในหัวข้อนี้: " รูปแบบการออกแบบฟลายเวท "
รูปแบบการออกแบบใน Java - 7

รูปแบบพฤติกรรม

ดังนั้นเราจึงพบว่าสามารถสร้างออบเจ็กต์ได้อย่างไรและสามารถจัดระเบียบการเชื่อมต่อระหว่างคลาสได้อย่างไร สิ่งที่น่าสนใจที่สุดที่เหลืออยู่คือการให้ความยืดหยุ่นในการเปลี่ยนแปลงพฤติกรรมของวัตถุ และรูปแบบพฤติกรรมจะช่วยเราในเรื่องนี้ รูปแบบหนึ่งที่ถูกกล่าวถึงบ่อยที่สุดคือรูปแบบ " กลยุทธ์ " นี่คือจุดเริ่มต้นของการศึกษารูปแบบในหนังสือ “ Head First. Design Patterns ” การใช้รูปแบบ "กลยุทธ์" ทำให้เราสามารถจัดเก็บภายในออบเจ็กต์ว่าเราจะดำเนินการอย่างไร เช่น วัตถุภายในเก็บกลยุทธ์ที่สามารถเปลี่ยนแปลงได้ระหว่างการเรียกใช้โค้ด นี่เป็นรูปแบบที่เรามักใช้เมื่อใช้ตัวเปรียบเทียบ:
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);
  }
}
ก่อนเรา - TreeSet. มีพฤติกรรมTreeSetการรักษาลำดับขององค์ประกอบเช่น เรียงลำดับพวกมัน (เนื่องจากเป็น SortedSet) ลักษณะการทำงานนี้มีกลยุทธ์เริ่มต้น ซึ่งเราเห็นใน JavaDoc: การเรียงลำดับใน "การเรียงลำดับตามธรรมชาติ" (สำหรับสตริง นี่คือการเรียงลำดับพจนานุกรม) สิ่งนี้จะเกิดขึ้นหากคุณใช้ตัวสร้างแบบไม่มีพารามิเตอร์ แต่ถ้าเราอยากเปลี่ยนกลยุทธ์เราก็ผ่านComparatorได้ ในตัวอย่างนี้ เราสามารถสร้างชุดของเราเป็นnew TreeSet(comparator)จากนั้นลำดับการจัดเก็บองค์ประกอบ (กลยุทธ์การจัดเก็บ) จะเปลี่ยนไปเป็นลำดับที่ระบุในตัวเปรียบเทียบ ที่น่าสนใจคือมีรูปแบบเดียวกันเกือบเรียกว่า " รัฐ " รูปแบบ "สถานะ" บอกว่าถ้าเรามีพฤติกรรมบางอย่างในวัตถุหลักที่ขึ้นอยู่กับสถานะของวัตถุนี้ เราก็สามารถอธิบายสถานะนั้นว่าเป็นวัตถุและเปลี่ยนวัตถุสถานะได้ และมอบหมายการโทรจากวัตถุหลักไปยังรัฐ อีกรูปแบบหนึ่งที่เรารู้จักจากการศึกษาพื้นฐานของภาษา Java คือรูปแบบ “ Command ” รูปแบบการออกแบบนี้แสดงให้เห็นว่าคำสั่งที่แตกต่างกันสามารถแสดงเป็นคลาสที่แตกต่างกันได้ รูปแบบนี้คล้ายกับรูปแบบกลยุทธ์มาก แต่ในรูปแบบกลยุทธ์ เรากำลังกำหนดวิธีดำเนินการเฉพาะเจาะจงใหม่ (เช่น การเรียงลำดับในTreeSet) ในรูปแบบ "คำสั่ง" เราจะกำหนดการกระทำที่จะดำเนินการใหม่ คำสั่งรูปแบบอยู่กับเราทุกวันเมื่อเราใช้เธรด:
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();
  }
}
อย่างที่คุณเห็น command กำหนดการกระทำหรือคำสั่งที่จะดำเนินการในเธรดใหม่ นอกจากนี้ยังควรพิจารณารูปแบบ " สายโซ่แห่ง ความรับผิดชอบ " ด้วย รูปแบบนี้ก็ง่ายมากเช่นกัน รูปแบบนี้บอกว่าหากบางสิ่งจำเป็นต้องได้รับการประมวลผล คุณสามารถรวบรวมตัวจัดการแบบลูกโซ่ได้ ตัวอย่างเช่น รูปแบบนี้มักใช้ในเว็บเซิร์ฟเวอร์ ที่อินพุต เซิร์ฟเวอร์ได้รับคำขอบางอย่างจากผู้ใช้ คำขอนี้จะต้องผ่านห่วงโซ่การประมวลผล สายของตัวจัดการนี้ประกอบด้วยตัวกรอง (เช่น ไม่ยอมรับคำขอจากบัญชีดำของที่อยู่ IP) ตัวจัดการการตรวจสอบสิทธิ์ (อนุญาตเฉพาะผู้ใช้ที่ได้รับอนุญาตเท่านั้น) ตัวจัดการส่วนหัวของคำขอ ตัวจัดการแคช ฯลฯ แต่มีตัวอย่างที่ง่ายกว่าและเข้าใจง่ายกว่าใน 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");
  }
}
อย่างที่คุณเห็น ตัวจัดการจะถูกเพิ่มเข้าไปในรายการตัวจัดการตัวบันทึก เมื่อคนบันทึกได้รับข้อความสำหรับการประมวลผล แต่ละข้อความดังกล่าวจะผ่านสายโซ่ของตัวจัดการ (จากlogger.getHandlers) สำหรับคนตัดไม้นั้น อีกรูปแบบหนึ่งที่เราเห็นทุกวันคือ “ Iterator ” สาระสำคัญของมันคือการแยกคอลเลกชันของวัตถุ (เช่น คลาสที่แสดงโครงสร้างข้อมูล ตัวอย่างเช่นList) และการสำรวจเส้นทางของคอลเลกชันนี้
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());
    }
  }
}
อย่างที่คุณเห็น ตัววนซ้ำไม่ได้เป็นส่วนหนึ่งของคอลเลกชัน แต่แสดงโดยคลาสที่แยกจากกันที่สำรวจคอลเลกชัน ผู้ใช้ตัววนซ้ำอาจไม่รู้ด้วยซ้ำว่าคอลเลกชันใดกำลังวนซ้ำอยู่ เช่น เขาไปเยี่ยมชมคอลเลกชันอะไร? ควรพิจารณารูปแบบ " ผู้เยี่ยมชม " รูปแบบผู้เยี่ยมชมจะคล้ายกับรูปแบบตัววนซ้ำมาก รูปแบบนี้ช่วยให้คุณข้ามโครงสร้างของวัตถุและดำเนินการกับวัตถุเหล่านี้ได้ พวกเขาแตกต่างกันค่อนข้างในแนวคิด ตัววนซ้ำจะสำรวจคอลเลกชันเพื่อให้ไคลเอนต์ที่ใช้ตัววนซ้ำไม่สนใจว่าคอลเลกชันนั้นอยู่ภายในอะไร เฉพาะองค์ประกอบในลำดับเท่านั้นที่สำคัญ ผู้เยี่ยมชมหมายความว่ามีลำดับชั้นหรือโครงสร้างของวัตถุที่เราเยี่ยมชม ตัวอย่างเช่น เราสามารถใช้การประมวลผลไดเร็กทอรีแยกกันและการประมวลผลไฟล์แยกกัน Java มีการนำรูปแบบนี้ไปใช้นอกกรอบในรูปแบบ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
    }
  }
}
บางครั้งจำเป็นต้องมีวัตถุบางอย่างเพื่อตอบสนองต่อการเปลี่ยนแปลงในวัตถุอื่น จากนั้นรูปแบบ "ผู้สังเกตการณ์" จะช่วยเรา วิธีที่สะดวกที่สุดคือการจัดหากลไกการสมัครสมาชิกที่อนุญาตให้บางอ็อบเจ็กต์ตรวจสอบและตอบสนองต่อเหตุการณ์ที่เกิดขึ้นในอ็อบเจ็กต์อื่น รูปแบบนี้มักใช้กับผู้ฟังและผู้สังเกตการณ์ต่างๆ ที่ตอบสนองต่อเหตุการณ์ต่างๆ เป็นตัวอย่างง่ายๆ เราสามารถจำการนำรูปแบบนี้ไปใช้ตั้งแต่เวอร์ชันแรกของ 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!");
  }
}
มีรูปแบบพฤติกรรมที่มีประโยชน์อีกรูปแบบหนึ่ง - “ ผู้ไกล่เกลี่ย ” มันมีประโยชน์เพราะในระบบที่ซับซ้อน จะช่วยลบการเชื่อมต่อระหว่างอ็อบเจ็กต์ต่างๆ และมอบหมายการโต้ตอบทั้งหมดระหว่างอ็อบเจ็กต์ให้กับบางอ็อบเจ็กต์ ซึ่งเป็นตัวกลาง หนึ่งในการใช้งานที่โดดเด่นที่สุดของรูปแบบนี้คือ Spring MVC ซึ่งใช้รูปแบบนี้ คุณสามารถอ่านเพิ่มเติมเกี่ยวกับสิ่งนี้ได้ที่นี่: " Spring: Mediator Pattern " คุณมักจะเห็นสิ่งเดียวกันนี้ในตัวอย่าง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);
  }
}
ตัวอย่างดูเหมือนรูปแบบคำสั่งมากกว่า และแก่นแท้ของรูปแบบ "ผู้ไกล่เกลี่ย" ถูกซ่อนอยู่ในการนำTimer"a" ไปใช้ ภายในตัวจับเวลาจะมีคิวงานTaskQueueและมีTimerThreadเธรด เราในฐานะลูกค้าของคลาสนี้ ไม่ได้โต้ตอบกับพวกเขา แต่โต้ตอบกับTimerอ็อบเจ็กต์ ซึ่งตอบสนองต่อการเรียกเมธอดของเรา เข้าถึงเมธอดของอ็อบเจ็กต์อื่น ๆ ที่มันเป็นตัวกลาง ภายนอกอาจดูคล้ายกับ "Facade" มาก แต่ความแตกต่างก็คือเมื่อใช้ Facade ส่วนประกอบจะไม่รู้ว่า Facade มีอยู่จริงและพูดคุยกัน และเมื่อใช้ "ตัวกลาง" องค์ประกอบจะรู้และใช้ตัวกลางแต่ไม่ได้ติดต่อกันโดยตรง ควรพิจารณารูปแบบ “ Template Method ” รูปแบบชัดเจนจากชื่อ บรรทัดล่างคือโค้ดถูกเขียนในลักษณะที่ผู้ใช้โค้ด (นักพัฒนา) ได้รับเทมเพลตอัลกอริทึมบางส่วนซึ่งขั้นตอนต่างๆ ได้รับอนุญาตให้กำหนดใหม่ได้ สิ่งนี้ช่วยให้ผู้ใช้โค้ดไม่ต้องเขียนอัลกอริธึมทั้งหมด แต่ให้คิดเฉพาะวิธีการดำเนินการขั้นตอนหนึ่งของอัลกอริธึมนี้อย่างถูกต้องเท่านั้น ตัวอย่างเช่น Java มีคลาสนามธรรมAbstractListที่กำหนดพฤติกรรมของตัววนซ้ำListด้วย อย่างไรก็ตาม ตัว วนซ้ำเองก็ใช้เมธอดลีฟ เช่น: get, set, ลักษณะการทำงานของวิธีการเหล่า นี้removeถูกกำหนดโดยนักพัฒนาของลูกหลาน AbstractListดังนั้นตัววนซ้ำในAbstractList- จึงเป็นเทมเพลตสำหรับอัลกอริทึมสำหรับการวนซ้ำบนชีต และผู้พัฒนาการใช้งานเฉพาะAbstractListเปลี่ยนพฤติกรรมของการวนซ้ำนี้โดยการกำหนดพฤติกรรมของขั้นตอนเฉพาะ รูปแบบสุดท้ายที่เราวิเคราะห์คือรูปแบบ “ Snapshot ” (Momento) สาระสำคัญของมันคือการรักษาสถานะบางอย่างของวัตถุด้วยความสามารถในการฟื้นฟูสถานะนี้ ตัวอย่างที่เป็นที่รู้จักมากที่สุดจาก JDK คือการทำให้เป็นอนุกรมของอ็อบเจ็กต์ เช่น java.io.Serializable. ลองดูตัวอย่าง:
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
    }
  }
}
รูปแบบการออกแบบใน Java - 8

บทสรุป

ดังที่เราเห็นจากการรีวิวพบว่ามีลวดลายที่หลากหลายมาก แต่ละคนแก้ปัญหาของตัวเอง และการรู้รูปแบบเหล่านี้สามารถช่วยให้คุณเข้าใจวิธีเขียนระบบของคุณได้อย่างทันท่วงที เพื่อให้มีความยืดหยุ่น บำรุงรักษาได้ และทนทานต่อการเปลี่ยนแปลง และสุดท้ายคือลิงก์บางส่วนสำหรับการเจาะลึก: #เวียเชสลาฟ
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION