รูปแบบการออกแบบแอปพลิเคชันบนเว็บ [ปิด]


359

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

ที่จริงแล้วฉันมีเอนทิตี้น้อยในหน้าแรกของฉันและสอดคล้องกับแต่ละรายการเรามีตัวเลือกไม่กี่อย่างเช่นเพิ่มแก้ไขและลบ ก่อนหน้านี้ฉันใช้ Servlet หนึ่งตัวต่อหนึ่งตัวเลือกเช่น Servlet1 สำหรับเพิ่มเอนทิตี้ 1, Servlet2 สำหรับแก้ไขเอนทิตี 1 และต่อ ๆ ไปและด้วยวิธีนี้เราได้รับ servlet จำนวนมาก

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


8
ไม่ใช่รูปแบบการออกแบบที่เป็นทางการจริงๆ แต่อย่าลืม PRG (post-redirect-get) และ Hijax (ใช้งานได้โดยไม่ต้อง js ก่อนจากนั้นจี้ลิงก์และปุ่มด้วย ajax)
Neil McGuigan

คำตอบ:


488

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


รูปแบบตัวควบคุมมุมมองแบบจำลอง

แกน (สถาปัตยกรรม) รูปแบบการออกแบบที่คุณต้องการใช้เป็นรูปแบบ Model-View-Controller ควบคุมจะได้รับการแสดงโดย Servlet ซึ่ง (ใน) สร้างโดยตรง / ใช้เฉพาะรุ่นและดูอยู่บนพื้นฐานของการร้องขอ ตัวแบบจะถูกแสดงโดยคลาส Javabean ซึ่งมักจะสามารถแบ่งได้เพิ่มเติมในรูปแบบธุรกิจซึ่งมีการกระทำ (พฤติกรรม) และรูปแบบข้อมูลซึ่งมีข้อมูล (ข้อมูล) ดูคือการเป็นตัวแทนจากไฟล์ JSP ที่มีการเข้าถึงโดยตรงไป ( ข้อมูล ) รุ่นโดย EL (Expression Language)

จากนั้นจะมีการเปลี่ยนแปลงตามวิธีการจัดการการกระทำและกิจกรรม สิ่งที่ได้รับความนิยม ได้แก่ :

  • MVC ที่อิงตามคำขอ (การกระทำ) : นี่เป็นวิธีที่ง่ายที่สุดในการนำไปใช้ (การธุรกิจ ) รุ่นทำงานโดยตรงกับHttpServletRequestและHttpServletResponseวัตถุ คุณต้องรวบรวมแปลงและตรวจสอบพารามิเตอร์คำขอ (ส่วนใหญ่) ด้วยตัวคุณเอง ดูสามารถแสดงด้วยวานิลลาธรรมดา HTML / CSS / JS และไม่รักษาสถานะระหว่างการร้องขอ นี่คือวิธีที่หมู่คนอื่น ๆฤดูใบไม้ผลิ MVC , Strutsและลายเส้นแนวพระราชดำริ

  • MVC ที่อิงส่วนประกอบ : สิ่งนี้ยากที่จะนำไปใช้ แต่คุณท้ายด้วยรูปแบบที่เรียบง่ายกว่าเดิมและดูว่า Servlet API "ดิบ" ทั้งหมดถูกแยกออกไปอย่างสิ้นเชิง คุณไม่จำเป็นต้องรวบรวมแปลงและตรวจสอบพารามิเตอร์คำขอด้วยตัวเอง ควบคุมไม่งานนี้และชุดรวบรวมและตรวจสอบแปลงค่าคำขอในรุ่น สิ่งที่คุณต้องทำคือการกำหนดวิธีการกระทำที่ทำงานโดยตรงกับคุณสมบัติของรูปแบบ ดูเป็นตัวแทนจาก "ส่วนประกอบ" ในรสชาติของ taglibs JSP หรือองค์ประกอบ XML ซึ่งจะสร้าง HTML / CSS / JS สถานะของมุมมองสำหรับคำขอที่ตามมาจะได้รับการปรับปรุงในเซสชัน สิ่งนี้มีประโยชน์อย่างยิ่งสำหรับเหตุการณ์การแปลงการตรวจสอบความถูกต้องและการเปลี่ยนแปลงค่า นี่คือวิธีอื่น ๆ ในกลุ่มJSF , Wicket and Play! โรงงาน

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

ในคำอธิบายโดยละเอียดด้านล่างฉันจะ จำกัด ตัวเองให้ขอ MVC ตามเนื่องจากง่ายต่อการใช้งาน


รูปแบบตัวควบคุมด้านหน้า ( รูปแบบสื่อกลาง )

อันดับแรกส่วนคอนโทรลเลอร์ควรใช้รูปแบบ Front Controller (ซึ่งเป็นรูปแบบเฉพาะของMediator ) ควรประกอบด้วยเซิร์ฟเล็ตเพียงอันเดียวซึ่งมีจุดเข้าใช้งานส่วนกลางของการร้องขอทั้งหมด ควรสร้างแบบจำลองตามข้อมูลที่มีอยู่โดยการร้องขอเช่น pathinfo หรือ servletpath วิธีการและ / หรือพารามิเตอร์เฉพาะ รูปแบบธุรกิจที่เรียกว่าActionในด้านล่างHttpServletตัวอย่างเช่น

protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    try {
        Action action = ActionFactory.getAction(request);
        String view = action.execute(request, response);

        if (view.equals(request.getPathInfo().substring(1)) {
            request.getRequestDispatcher("/WEB-INF/" + view + ".jsp").forward(request, response);
        }
        else {
            response.sendRedirect(view); // We'd like to fire redirect in case of a view change as result of the action (PRG pattern).
        }
    }
    catch (Exception e) {
        throw new ServletException("Executing action failed.", e);
    }
}

การดำเนินการแอ็คชันควรส่งคืนตัวบ่งชี้บางตัวเพื่อค้นหามุมมอง ที่ง่ายที่สุดคือใช้เป็นชื่อไฟล์ของ JSP แผนที่เซิร์ฟเล็ตนี้ในที่เฉพาะเจาะจงurl-patternในweb.xmlเช่น/pages/*, หรือแม้เพียงแค่*.do*.html

ในกรณีที่คำนำหน้า - รูปแบบเป็นตัวอย่าง/pages/*คุณสามารถเรียก URL เช่นนั้นได้ http://example.com/pages/register , http://example.com/pages/loginฯลฯ และให้/WEB-INF/register.jsp, /WEB-INF/login.jspกับ GET ที่เหมาะสมและการกระทำ POST . ชิ้นส่วนregisterและloginอื่น ๆ จะมีให้ใช้งานตามrequest.getPathInfo()ตัวอย่างด้านบน

เมื่อคุณใช้คำต่อท้ายรูปเหมือน*.do, *.htmlฯลฯ แล้วคุณจะทำได้แล้ววิงวอน URL เช่น http://example.com/register.do , http://example.com/login.doฯลฯ และคุณควรจะเปลี่ยน ตัวอย่างรหัสในคำตอบนี้ (ยังActionFactory) เพื่อแยกregisterและloginชิ้นส่วนโดยrequest.getServletPath()แทน


รูปแบบกลยุทธ์

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

public interface Action {
    public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception;
}

คุณอาจต้องการที่จะทำให้เฉพาะเจาะจงมากขึ้นด้วยข้อยกเว้นที่กำหนดเองเช่นException ActionExceptionมันเป็นแค่ตัวอย่างกำหนดการพื้นฐานส่วนที่เหลือนั้นขึ้นอยู่กับคุณ

นี่คือตัวอย่างของการบันทึกLoginAction(ในชื่อของมัน) ในผู้ใช้ Userตัวเองอยู่ในการเปิดรูปแบบข้อมูล ดูUserตระหนักถึงการปรากฏตัวของ

public class LoginAction implements Action {

    public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        User user = userDAO.find(username, password);

        if (user != null) {
            request.getSession().setAttribute("user", user); // Login user.
            return "home"; // Redirect to home page.
        }
        else {
            request.setAttribute("error", "Unknown username/password. Please retry."); // Store error message in request scope.
            return "login"; // Go back to redisplay login form with error.
        }
    }

}

รูปแบบวิธีการของโรงงาน

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

public static Action getAction(HttpServletRequest request) {
    return actions.get(request.getMethod() + request.getPathInfo());
}

actionsในทางกลับกันควรจะมีบางคงที่ / applicationwide Map<String, Action>ซึ่งถือการกระทำที่รู้จักกันทั้งหมด มันขึ้นอยู่กับคุณว่าจะเติมแผนที่นี้อย่างไร hardcoding:

actions.put("POST/register", new RegisterAction());
actions.put("POST/login", new LoginAction());
actions.put("GET/logout", new LogoutAction());
// ...

หรือกำหนดค่าได้ตามไฟล์คุณสมบัติ / XML ใน classpath: (หลอก)

for (Entry entry : configuration) {
    actions.put(entry.getKey(), Class.forName(entry.getValue()).newInstance());
}

หรือแบบไดนามิกโดยอาศัยการสแกนใน classpath สำหรับคลาสที่ใช้อินเทอร์เฟซและ / หรือหมายเหตุประกอบที่แน่นอน: (หลอก)

for (ClassFile classFile : classpath) {
    if (classFile.isInstanceOf(Action.class)) {
       actions.put(classFile.getAnnotation("mapping"), classFile.newInstance());
    }
}

อย่าลืมสร้าง "ไม่ทำอะไรเลย" Actionสำหรับกรณีที่ไม่มีการแมป ปล่อยให้มันกลับตัวอย่างโดยตรงrequest.getPathInfo().substring(1)แล้ว


รูปแบบอื่น ๆ

นั่นเป็นรูปแบบที่สำคัญจนถึงตอนนี้

ในการก้าวไปอีกขั้นคุณสามารถใช้รูปแบบ Facadeเพื่อสร้างContextคลาสซึ่งจะล้อมวัตถุคำขอและตอบสนองและเสนอวิธีการที่สะดวกหลายอย่างที่มอบหมายให้กับวัตถุคำขอและตอบสนองและส่งผ่านเป็นอาร์กิวเมนต์ไปยังAction#execute()เมธอดแทน สิ่งนี้จะเพิ่มเลเยอร์นามธรรมพิเศษเพื่อซ่อน raw servlet API ออกไป จากนั้นคุณควรจะจบลงด้วยการประกาศเป็นศูนย์ import javax.servlet.*ในActionการใช้งานทุกครั้ง ในแง่ JSF นี่คือสิ่งที่FacesContextและExternalContextชั้นเรียนกำลังทำ คุณสามารถหาตัวอย่างที่เป็นรูปธรรมได้ในคำตอบนี้

จากนั้นมีรูปแบบสถานะสำหรับกรณีที่คุณต้องการเพิ่มเลเยอร์นามธรรมพิเศษเพื่อแบ่งงานของการรวบรวมพารามิเตอร์คำขอแปลงพวกเขาตรวจสอบพวกเขาปรับปรุงค่ารุ่นและดำเนินการการกระทำ ในแง่ของ JSF นี่คือสิ่งที่LifeCycleกำลังทำอยู่

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

วิธีนี้คุณสามารถวิวัฒนาการทีละนิดไปสู่เฟรมเวิร์กที่ใช้องค์ประกอบ


ดูสิ่งนี้ด้วย:


4
@masato: คุณสามารถทำเช่นนี้ในบล็อกแบบ initializer คงที่
BalusC

1
@masato: โดยวิธีการถ้าคุณต้องการที่จะดึงพวกเขาจากweb.xmlนั้นคุณสามารถใช้ServletContextListenerสำหรับการนี้ ให้โรงงานนำไปใช้ (และลงทะเบียน<listener>ในweb.xml) และทำหน้าที่เติมระหว่างcontextInitialized()วิธีการ
BalusC

3
ทำงานที่ "post_servlet" ควรดำเนินการแทน คุณไม่ควรมีมากกว่าหนึ่งเซิร์ฟเล็ต สิ่งธุรกิจควรทำในคลาสการดำเนินการ หากคุณต้องการให้เป็นคำขอใหม่ให้กลับไปที่มุมมองอื่นซึ่งจะทำให้เกิดการเปลี่ยนเส้นทางและทำงานในการดำเนินการใหม่ที่เชื่อมโยงกับคำขอ GET
BalusC

2
ขึ้นอยู่กับ ที่ง่ายที่สุดคือการทำให้ถูกต้องในActionการนำไปใช้เช่นเดียวกับ servlets ปกติ (ดูที่servlets wikiสำหรับตัวอย่างพื้นฐานซึ่งคุณมีอิสระในการปรับโครงสร้างเพิ่มเติมในValidatorอินเตอร์เฟสบางส่วน) แต่คุณสามารถทำได้ก่อนที่จะเรียกใช้การกระทำ แต่สิ่งนี้มีความซับซ้อนมากขึ้นเนื่องจากต้องมีการตรวจสอบกฎการตรวจสอบตามแต่ละมุมมอง JSF ได้ครอบคลุมนี้โดยนำเสนอrequired="true", validator="customValidatorName"ฯลฯ ในมาร์กอัป XHTML ซึ่ง
BalusC

2
@AndreyBotalov: ตรวจสอบซอร์สโค้ดของเฟรมเวิร์ก MVC เช่น JSF, Spring MVC, Wicket, Struts2 เป็นต้นพวกมันล้วนเป็นโอเพ่นซอร์ส
BalusC

13

ในรูปแบบ MVC ที่ถูกตี Servlet คือ "C" - คอนโทรลเลอร์

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

แม้ว่าฉันจะไม่เริ่มเขียนServletชั้นเรียนดิบ งานที่พวกเขาทำนั้นสามารถคาดเดาได้และสร้างสำเร็จรูปซึ่งเป็นสิ่งที่กรอบงานทำได้ดีมาก โชคดีที่มีผู้สมัครจำนวนมากที่มีเวลาทดสอบ (ตามลำดับตัวอักษร): Apache Wicket , Java Server Faces , Springเพื่อชื่อไม่กี่


5

IMHO มีความแตกต่างไม่มากนักในกรณีของเว็บแอปพลิเคชันถ้าคุณดูจากมุมของการมอบหมายความรับผิดชอบ อย่างไรก็ตามรักษาความชัดเจนในเลเยอร์ เก็บทุกอย่างไว้เพื่อวัตถุประสงค์ในการนำเสนอในเลเยอร์การนำเสนอเช่นตัวควบคุมและรหัสเฉพาะสำหรับการควบคุมเว็บ เพียงแค่ให้เอนทิตีของคุณอยู่ในเลเยอร์ธุรกิจและฟีเจอร์ทั้งหมด (เช่นเพิ่มแก้ไขลบ) ฯลฯ ในเลเยอร์ธุรกิจ อย่างไรก็ตามการแสดงผลพวกเขาบนเบราว์เซอร์ที่จะจัดการในเลเยอร์การนำเสนอ สำหรับ. Net รูปแบบ ASP.NET MVC นั้นดีมากในแง่ของการแยกเลเยอร์ มองไปที่รูปแบบ MVC


คุณช่วยอธิบายอะไรได้บ้างใน servlet
mawia

servlet ควรเป็นคอนโทรลเลอร์ถ้าคุณใช้ MVC
Kangkan

3

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

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

2) รูปแบบที่ใช้ในการถ่ายโอนข้อมูลระหว่างการกระทำและหน้า jsp อ็อบเจ็กต์นี้ควรประกอบด้วยชุดของ getter และ setters สำหรับแอ็ตทริบิวต์ที่จำเป็นต้องเข้าถึงไฟล์ jsp แบบฟอร์มยังมีวิธีการตรวจสอบข้อมูลก่อนที่จะได้รับการยืนยัน

3) หน้า jsp ซึ่งใช้เพื่อแสดง HTML สุดท้ายของหน้า หน้า jsp เป็นไฮบริดของ HTML และแท็ก struts พิเศษที่ใช้ในการเข้าถึงและจัดการข้อมูลในแบบฟอร์ม แม้ว่า struts อนุญาตให้ผู้ใช้แทรกโค้ด Java ลงในไฟล์ jsp คุณควรระมัดระวังในการทำเช่นนั้นเพราะจะทำให้โค้ดของคุณอ่านยากขึ้น โค้ด Java ภายในไฟล์ jsp นั้นยากต่อการดีบักและไม่สามารถทดสอบหน่วยได้ หากคุณพบว่าคุณเขียนโค้ด java มากกว่า 4-5 บรรทัดภายในไฟล์ jsp โค้ดอาจจะถูกย้ายไปที่แอ็คชัน


หมายเหตุ: ใน Struts 2 วัตถุแบบฟอร์มเรียกว่าแบบจำลองแทน แต่ทำงานในลักษณะเดียวกับที่ฉันอธิบายไว้ในคำตอบดั้งเดิมของฉัน
EsotericNonsense

3

คำตอบที่ยอดเยี่ยมของBalusCครอบคลุมรูปแบบส่วนใหญ่สำหรับเว็บแอปพลิเคชัน

บางแอปพลิเคชันอาจต้องการChain-of-responsibility_pattern

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

ใช้กรณีเพื่อใช้รูปแบบนี้:

เมื่อตัวจัดการการประมวลผลการร้องขอ (คำสั่ง) ไม่เป็นที่รู้จักและคำขอนี้สามารถส่งไปยังวัตถุหลาย ๆ โดยทั่วไปคุณตั้งค่าตัวตายตัวแทนเป็นคัดค้าน หากวัตถุปัจจุบันไม่สามารถจัดการคำขอหรือประมวลผลคำขอบางส่วนและส่งต่อคำขอเดียวกันไปยังผู้สืบทอดวัตถุที่รับ

คำถาม / บทความ SE ที่มีประโยชน์:

เหตุใดฉันจึงต้องใช้เครือข่ายความรับผิดชอบเหนือมัณฑนากร

ประเพณีทั่วไปสำหรับห่วงโซ่ความรับผิดชอบ?

ห่วงโซ่ของความรับผิดชอบในรูปแบบจาก oodesign

chain_of_responsibilityจากแหล่งที่มา

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