วิธีการแยกคลาสที่มีขนาดใหญ่และแน่นเข้าด้วยกันได้อย่างไร


14

ฉันมีชั้นเรียนขนาดใหญ่ที่มีโค้ดมากกว่า 2K บรรทัด (และเพิ่มขึ้นเรื่อย ๆ ) ที่ฉันอยากจะสร้างใหม่หากเป็นไปได้เพื่อให้มีการออกแบบที่เบาและสะอาดมากขึ้น

เหตุผลที่มันใหญ่มากส่วนใหญ่เป็นเพราะคลาสเหล่านี้จัดการกับชุดของแผนที่ที่วิธีการส่วนใหญ่จำเป็นต้องเข้าถึงและวิธีการเชื่อมต่อกันมาก

ฉันจะยกตัวอย่างชัดเจน: ฉันมีคลาสที่เรียกServerว่าจัดการข้อความขาเข้า มันมีวิธีการเช่นjoinChatroom, searchUsers, sendPrivateMessageและอื่น ๆ ทั้งหมดของวิธีการเหล่านี้จัดการแผนที่เช่นusers, chatrooms, servers...

บางทีมันอาจจะดีถ้าฉันมีข้อความการจัดการเรียนที่เกี่ยวข้องกับห้องสนทนาการจัดการกับผู้ใช้อื่น ๆ แต่ปัญหาหลักที่นี่คือฉันต้องใช้แผนที่ทั้งหมดในวิธีการส่วนใหญ่ นั่นเป็นเหตุผลที่ตอนนี้พวกเขาทั้งหมดติดอยู่ในServerชั้นเรียนเพราะพวกเขาทั้งหมดพึ่งพาแผนที่ทั่วไปเหล่านี้และวิธีการเชื่อมต่อซึ่งกันและกัน

ฉันจะต้องสร้างห้องเรียนแชท แต่มีการอ้างอิงถึงวัตถุอื่น ๆ ผู้ใช้คลาสอีกครั้งโดยอ้างอิงถึงวัตถุอื่นทั้งหมดเป็นต้น

ฉันรู้สึกว่าฉันจะทำอะไรผิด


หากคุณต้องการสร้างคลาสเช่น User และ Chatroom คลาสเหล่านี้ต้องการเพียงการอ้างอิงถึงโครงสร้างข้อมูลทั่วไปหรือพวกเขาจะอ้างอิงซึ่งกันและกันหรือไม่?

มีคำตอบที่น่าพอใจหลายข้อที่นี่คุณควรเลือกคำตอบหนึ่งข้อ
jeremyjjbrown

@ jeremyjjbrown คำถามถูกย้ายไปแล้วและฉันทำมันหาย เลือกคำตอบขอบคุณ
Matthew

คำตอบ:


10

จากคำอธิบายของคุณฉันจะเดาว่าแผนที่ของคุณเป็นถุงข้อมูลล้วนๆพร้อมกับตรรกะทั้งหมดในServerวิธีการ ด้วยการผลักดันตรรกะของห้องสนทนาทั้งหมดให้อยู่ในคลาสที่แยกกันคุณยังคงติดอยู่กับแผนที่ที่มีข้อมูลอยู่

ให้ลองสร้างแบบจำลองแต่ละห้องสนทนาผู้ใช้ ฯลฯ เป็นวัตถุแทน ด้วยวิธีนี้คุณจะผ่านเฉพาะวัตถุเฉพาะที่จำเป็นสำหรับวิธีการบางอย่างแทนการแมปข้อมูลขนาดใหญ่

ตัวอย่างเช่น:

public class User {
  private String name;
  ...

  public void sendMessage(String message) {
    ...
  }
}

public class Chatroom {
  // users in this chatroom
  private Collection<User> users;

  public void add(User user) {
    users.add(user);
  }

  public void sendMessage(String msg) {
    for (User user : users)
      user.sendMessage(msg);
  }
}

public class Server {
  // all users on the server
  private Collection<User> users;

  // all chatrooms on the server
  private Collection<Chatroom> chatrooms;

  /* methods to handle incoming messages */
}

ตอนนี้คุณสามารถโทรหาวิธีการเฉพาะเพื่อจัดการกับข้อความได้ง่ายๆ:

ต้องการเข้าร่วมห้องแชทหรือไม่?

chatroom.add(user);

ต้องการส่งข้อความส่วนตัวหรือไม่

user.sendMessage(msg);

ต้องการส่งข้อความสาธารณะ?

chatroom.sendMessage(msg);

5

คุณควรจะสามารถสร้างคลาสที่เก็บแต่ละคอลเลกชัน ในขณะที่Serverจะต้องมีการอ้างอิงถึงแต่ละคอลเลกชันเหล่านี้จะต้องมีจำนวนตรรกะที่น้อยที่สุดซึ่งจะไม่เกี่ยวข้องกับการเข้าถึงหรือรักษาคอลเลกชันพื้นฐาน สิ่งนี้จะทำให้เห็นได้ชัดเจนยิ่งขึ้นว่าเซิร์ฟเวอร์กำลังทำอะไรและแยกออกมาเป็นอย่างไร


4

เมื่อฉันได้เห็นชั้นเรียนขนาดใหญ่เช่นนี้ฉันได้พบว่ามักจะมีชั้นเรียน (หรือมากกว่า) ในการพยายามออกไป หากคุณรู้วิธีการที่คุณคิดว่าอาจไม่เกี่ยวข้องกับคลาสนี้ให้ทำให้คงที่ คอมไพเลอร์จะบอกวิธีการอื่น ๆ ที่ methd นี้เรียกใช้ Java จะยืนยันว่าพวกเขาจะคงที่เช่นกัน คุณทำให้พวกเขาคงที่ คอมไพเลอร์อีกครั้งจะบอกคุณถึงวิธีการใด ๆ ที่มันเรียก คุณทำเช่นนี้ซ้ำแล้วซ้ำอีกจนกว่าคุณจะไม่มีความล้มเหลวในการคอมไพล์อีกต่อไป จากนั้นคุณจะมีวิธีการโหลดมากมายในชั้นเรียนขนาดใหญ่ของคุณ ตอนนี้คุณสามารถดึงสิ่งเหล่านี้ออกไปสู่คลาสใหม่และทำให้วิธีการไม่คงที่ จากนั้นคุณสามารถเรียกคลาสใหม่นี้จากคลาสใหญ่เดิมของคุณ (ซึ่งควรมีบรรทัดน้อยกว่า)

จากนั้นคุณสามารถทำซ้ำกระบวนการจนกว่าคุณจะพอใจกับการออกแบบชั้นเรียน

หนังสือของมาร์ตินฟาวเลอร์เป็นหนังสือที่อ่านดีมากดังนั้นฉันอยากจะแนะนำเรื่องนี้เช่นกันเพราะมีบางครั้งที่คุณไม่สามารถใช้กลอุบายแบบคงที่นี้ได้


1
หนังสือของ Martin Fowler นี้ martinfowler.com/books/refactoring.html
Arul

1

เนื่องจากรหัสของคุณส่วนใหญ่มีอยู่ฉันขอแนะนำให้ใช้คลาสตัวช่วยเพื่อย้ายเมธอดของคุณ ที่จะช่วยให้ปรับโครงสร้างได้ง่าย ดังนั้นคลาสเซิร์ฟเวอร์ของคุณจะยังคงมีแผนที่อยู่ในนั้น แต่มันใช้ประโยชน์จากคลาสผู้ช่วยพูดว่า ChatroomHelper ด้วยวิธีการเช่นเข้าร่วม (แผนที่ห้องสนทนาผู้ใช้ String), รายชื่อ getUsers (แผนที่ห้องสนทนา), แผนที่ getChatrooms (ผู้ใช้ String)

คลาสเซิร์ฟเวอร์จะเก็บอินสแตนซ์ของ ChatroomHelper, UserHelper และอื่น ๆ ดังนั้นจึงย้ายเมธอดไปยังคลาสตัวช่วยแบบลอจิคัล ด้วยวิธีนี้คุณสามารถปล่อยให้วิธีสาธารณะในเซิร์ฟเวอร์เหมือนเดิมดังนั้นผู้โทรไม่จำเป็นต้องเปลี่ยน


1

หากต้องการเพิ่มคำตอบที่ลึกซึ้งของคาซาบลังกา - หากหลายคลาสจำเป็นต้องทำสิ่งพื้นฐานเดียวกันกับเอนทิตีบางประเภท (การเพิ่มผู้ใช้ไปยังคอลเลกชันการจัดการข้อความ ฯลฯ ) กระบวนการเหล่านั้นควรแยกไว้ด้วยเช่นกัน

มีหลายวิธีในการทำเช่นนี้ - โดยการสืบทอดหรือการแต่งเพลง สำหรับการสืบทอดคุณสามารถใช้คลาสฐานนามธรรมกับวิธีที่เป็นรูปธรรมที่ให้เขตข้อมูลและฟังก์ชันการทำงานเพื่อจัดการผู้ใช้หรือข้อความตัวอย่างและมีเอนทิตีเช่นchatroomและuser(หรืออื่น ๆ ) ขยายคลาสเหล่านั้น

ด้วยเหตุผลต่าง ๆมันเป็นกฎทั่วไปที่ดีในการใช้การแต่งเพลงมากกว่าการสืบทอด คุณอาจใช้การประพันธ์เพื่อทำสิ่งนี้ได้หลายวิธี เนื่องจากการจัดการผู้ใช้หรือข้อความเป็นหน้าที่สำคัญของความรับผิดชอบของคลาสจึงสามารถแย้งได้ว่าการฉีดคอนสตรัคเตอร์นั้นเหมาะสมที่สุด วิธีนี้การพึ่งพามีความโปร่งใสและไม่สามารถสร้างวัตถุโดยไม่ต้องมีฟังก์ชั่นที่จำเป็น ถ้าวิธีที่ผู้ใช้หรือข้อความได้รับการจัดการมีแนวโน้มที่จะเปลี่ยนหรือจะขยายออกไปคุณควรจะพิจารณาการใช้สิ่งที่ต้องการรูปแบบกลยุทธ์

ในทั้งสองกรณีต้องแน่ใจว่าได้กำหนดรหัสไปยังส่วนต่อประสานไม่ใช่คลาสที่เป็นรูปธรรมเพื่อความยืดหยุ่น

ทุกอย่างที่กล่าวมาพิจารณาค่าใช้จ่ายของความซับซ้อนที่เพิ่มขึ้นเสมอเมื่อใช้รูปแบบดังกล่าว - หากคุณไม่ต้องการใช้มันอย่าใช้รหัส หากคุณรู้ว่าคุณอาจจะไม่เปลี่ยนวิธีการจัดการผู้ใช้ / ข้อความเสร็จสิ้นคุณไม่จำเป็นต้องมีความซับซ้อนเชิงโครงสร้างของรูปแบบกลยุทธ์ - แต่เพื่อแยกข้อกังวลออกจากกันและหลีกเลี่ยงการซ้ำซ้อน จากอินสแตนซ์ที่เป็นรูปธรรมที่ใช้มัน - และหากไม่มีเหตุผลใดที่มีความสำคัญมากกว่าในทางตรงกันข้ามให้รวบรวมผู้ใช้ของฟังก์ชั่นการจัดการ (ห้องสนทนาผู้ใช้) ดังกล่าวด้วยวัตถุที่จัดการ

ดังนั้นเพื่อสรุป:

  1. ตามที่คาซาบลังกาเขียน: แยกและแค็ปซูลห้องสนทนาผู้ใช้ ฯลฯ
  2. แยกฟังก์ชั่นทั่วไป
  3. พิจารณาการแยกฟังก์ชันการทำงานแต่ละส่วนเป็นการหย่าร้างแทนข้อมูล (รวมถึงการเข้าถึงและการกลายพันธุ์) จากการทำงานที่ซับซ้อนมากขึ้นกับแต่ละกรณีของข้อมูลหรือมวลรวมของมัน (ตัวอย่างเช่นบางสิ่งบางอย่างsearchUsersอาจเข้าไปในคอลเลกชันคลาส )

0

ดูสิฉันคิดว่าคำถามของคุณกว้างเกินไปที่จะตอบเนื่องจากเราไม่มีคำอธิบายที่สมบูรณ์เกี่ยวกับปัญหาดังนั้นการเสนอการออกแบบที่ดีที่มีความรู้เพียงเล็กน้อยจึงเป็นไปไม่ได้ ฉันสามารถยกตัวอย่างหนึ่งที่เกี่ยวข้องกับความกังวลของคุณเกี่ยวกับความไร้ประโยชน์ที่เป็นไปได้ของการออกแบบที่ดีกว่า

คุณบอกว่าคลาสเซิร์ฟเวอร์ของคุณและห้องเรียน Chatroom ในอนาคตของคุณแชร์ข้อมูลเกี่ยวกับผู้ใช้ แต่ข้อมูลนี้ควรแตกต่างกัน เซิร์ฟเวอร์อาจมีชุดของผู้ใช้ที่เชื่อมต่อกับมันในขณะที่ห้องแชทซึ่งเป็นของเซิร์ฟเวอร์นั้นมีชุดผู้ใช้ที่แตกต่างกันซึ่งเป็นชุดย่อยของชุดแรกโดยเฉพาะผู้ใช้ที่เข้าสู่ระบบในห้องสนทนาเฉพาะ

นี่ไม่ใช่ข้อมูลเดียวกันแม้ว่าชนิดข้อมูลจะเหมือนกันก็ตาม
มีข้อดีหลายประการในการออกแบบที่ดี

ฉันยังไม่ได้อ่านหนังสือดังกล่าวโดย Fowler แต่ฉันได้อ่านสิ่งอื่น ๆ โดย Folwer และมันก็แนะนำให้ฉันโดยคนที่ฉันไว้วางใจดังนั้นฉันรู้สึกสะดวกสบายพอที่จะเห็นด้วยกับคนอื่น ๆ


0

ความจำเป็นในการเข้าถึงแผนที่ไม่ได้แสดงให้เห็นถึงระดับเมกะ คุณต้องแยกลอจิกในหลายคลาสและแต่ละคลาสต้องมีเมธอด getMap เพื่อให้คลาสอื่นสามารถเข้าถึงแม็พได้


0

ฉันจะใช้คำตอบเดียวกับที่ฉันให้ไว้ที่อื่น: ใช้คลาสเสาหินและแบ่งความรับผิดชอบในชั้นเรียนอื่น ๆ ทั้ง DCI และรูปแบบผู้เข้าชมมีตัวเลือกที่ดีสำหรับการทำเช่นนั้น


-1

ในแง่ของซอฟแวร์ตัวชี้วัดระดับใหญ่คือกระเป๋า มีเอกสารมากมายที่พิสูจน์ข้อความนี้ ทำไมถึงเป็นอย่างนั้น? เพราะชั้นเรียนขนาดใหญ่ยากต่อการเข้าใจมากกว่าชั้นเรียนขนาดเล็กและต้องใช้เวลาในการแก้ไขมากขึ้น ยิ่งไปกว่านั้นคลาสใหญ่นั้นยากมากเมื่อคุณทำการทดสอบ และชั้นเรียนขนาดใหญ่นั้นยากสำหรับคุณเมื่อคุณต้องการนำกลับมาใช้ใหม่เพราะอาจมีสิ่งที่ไม่พึงประสงค์

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.