AngularJS: เข้าใจรูปแบบการออกแบบ


147

ในบริบทของโพสต์นี้โดย Igor Minar, ผู้นำของ AngularJS:

MVC VS MVVM VS MVP เป็นหัวข้อที่ถกเถียงกันอยู่ว่านักพัฒนาหลายคนสามารถใช้เวลาหลายชั่วโมงในการโต้วาทีและโต้เถียงกัน

สำหรับหลายปี AngularJS ได้ใกล้ชิดกับ MVC (หรือมากกว่าหนึ่งในฝั่งไคลเอ็นต์ของสายพันธุ์) แต่เมื่อเวลาผ่านไปและขอขอบคุณที่ refactorings จำนวนมากและการปรับปรุง API ก็ตอนนี้ใกล้ชิดกับMVVM - The $ ขอบเขตวัตถุอาจได้รับการพิจารณาViewModelว่าจะถูก ตกแต่งด้วยฟังก์ชั่นที่เราเรียกว่าเป็นตัวควบคุม

ความสามารถในการจัดประเภทเฟรมเวิร์กและนำไปไว้ในหนึ่งในที่เก็บ MV * มีข้อดีบางประการ มันสามารถช่วยให้นักพัฒนารู้สึกคุ้นเคยกับ apis มากขึ้นด้วยการทำให้ง่ายต่อการสร้างแบบจำลองทางจิตที่แสดงถึงแอปพลิเคชันที่ถูกสร้างขึ้นด้วยกรอบงาน นอกจากนี้ยังสามารถช่วยในการสร้างคำศัพท์ที่ใช้โดยนักพัฒนา

ต้องบอกว่าฉันอยากเห็นนักพัฒนาสร้างแอพ kick-ass ที่ได้รับการออกแบบมาอย่างดีและติดตามความกังวลมากกว่าที่จะเห็นพวกเขาเสียเวลาเถียงเกี่ยวกับเรื่องไร้สาระ MV * และด้วยเหตุผลนี้ผมขอประกาศ AngularJSจะเป็นกรอบ MVW - Model-View-สิ่งที่ ไม่ว่าอะไรจะเกิดขึ้นกับ " สิ่งใดก็ตามที่เหมาะกับคุณ "

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

มีคำแนะนำหรือแนวทางในการใช้รูปแบบการออกแบบ AngularJS MVW (Model-View-Anything) ในแอปพลิเคชันฝั่งไคลเอ็นต์หรือไม่?


upvoted สำหรับ ... กว่าที่พวกเขาเสียเวลาโต้เถียงเกี่ยวกับ MV * เรื่องไร้สาระ
Shirgill Farhan

1
คุณไม่ต้องการ Angular เพื่อติดตามรูปแบบการออกแบบคลาสคำ
UsefulBee

คำตอบ:


223

ขอบคุณแหล่งข้อมูลที่มีค่าจำนวนมากฉันได้รับคำแนะนำทั่วไปสำหรับการใช้งานส่วนประกอบในแอป AngularJS:


ตัวควบคุม

  • ควบคุมควรจะเป็นเพียงinterlayerระหว่างรูปแบบและมุมมอง พยายามทำให้ผอมที่สุด

  • ขอแนะนำอย่างยิ่งให้หลีกเลี่ยงตรรกะทางธุรกิจในคอนโทรลเลอร์ ควรย้ายไปที่โมเดล

  • ควบคุมอาจสื่อสารกับตัวควบคุมอื่น ๆ โดยใช้วิธีการภาวนา (เป็นไปได้เมื่อเด็กต้องการที่จะสื่อสารกับผู้ปกครอง) หรือ$ Emit , $ ออกอากาศและ$ เกี่ยวกับวิธีการ ข้อความที่ปล่อยออกมาและออกอากาศควรถูกเก็บไว้ให้น้อยที่สุด

  • คอนโทรลเลอร์ไม่ควรสนใจเกี่ยวกับการนำเสนอหรือการจัดการ DOM

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

  • ขอบเขตในคอนโทรลเลอร์ควรใช้สำหรับการรวมโมเดลด้วยมุมมองและการ
    ห่อหุ้มโมเดลมุมมองเป็นรูปแบบการออกแบบการนำเสนอโมเดล


ขอบเขต

ขอบเขตการรักษาเป็นแบบอ่านอย่างเดียวในแม่แบบและการเขียนเฉพาะในตัวควบคุม วัตถุประสงค์ของขอบเขตคือเพื่ออ้างอิงถึงโมเดลไม่ใช่โมเดล

เมื่อทำการรวมสองทิศทาง (โมเดล ng) ตรวจสอบให้แน่ใจว่าคุณไม่ได้ผูกกับคุณสมบัติขอบเขตโดยตรง


แบบ

รุ่นใน AngularJS เป็นซิงเกิลที่กำหนดโดยบริการ

รุ่นมีวิธีที่ยอดเยี่ยมในการแยกข้อมูลและการแสดงผล

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

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

  • โมเดลควรสรุปข้อมูลแอปพลิเคชันของคุณและจัดเตรียมAPI เพื่อเข้าถึงและจัดการข้อมูลนั้น

  • รุ่นควรเป็นแบบพกพาจึงสามารถเคลื่อนย้ายไปยังแอปพลิเคชันที่คล้ายกันได้อย่างง่ายดาย

  • ด้วยการแยกหน่วยของลอจิกในแบบจำลองของคุณคุณจะสามารถค้นหาอัปเดตและดูแลรักษาได้ง่ายขึ้น

  • รุ่นสามารถใช้วิธีการของแบบจำลองทั่วไปทั่วโลกที่ใช้กันทั่วไปสำหรับแอปพลิเคชันทั้งหมด

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

  • พยายามหลีกเลี่ยงการใช้ฟังเหตุการณ์ในแบบจำลอง มันทำให้ยากต่อการทดสอบและฆ่าแบบจำลองในแง่ของความรับผิดชอบหลักเดียว

การนำโมเดลไปใช้

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

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

ตัวอย่าง :

angular.module('search')
.factory( 'searchModel', ['searchResource', function (searchResource) {

  var itemsPerPage = 10,
  currentPage = 1,
  totalPages = 0,
  allLoaded = false,
  searchQuery;

  function init(params) {
    itemsPerPage = params.itemsPerPage || itemsPerPage;
    searchQuery = params.substring || searchQuery;
  }

  function findItems(page, queryParams) {
    searchQuery = queryParams.substring || searchQuery;

    return searchResource.fetch(searchQuery, page, itemsPerPage).then( function (results) {
      totalPages = results.totalPages;
      currentPage = results.currentPage;
      allLoaded = totalPages <= currentPage;

      return results.list
    });
  }

  function findNext() {
    return findItems(currentPage + 1);
  }

  function isAllLoaded() {
    return allLoaded;
  }

  // return public model API  
  return {
    /**
     * @param {Object} params
     */
    init: init,

    /**
     * @param {Number} page
     * @param {Object} queryParams
     * @return {Object} promise
     */
    find: findItems,

    /**
     * @return {Boolean}
     */
    allLoaded: isAllLoaded,

    /**
     * @return {Object} promise
     */
    findNext: findNext
  };
});

การสร้างอินสแตนซ์ใหม่

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

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

angular.module('car')
 .factory( 'carModel', ['carResource', function (carResource) {

  function Car(data) {
    angular.extend(this, data);
  }

  Car.prototype = {
    save: function () {
      // TODO: strip irrelevant fields
      var carData = //...
      return carResource.save(carData);
    }
  };

  function getCarById ( id ) {
    return carResource.getById(id).then(function (data) {
      return new Car(data);
    });
  }

  // the public API
  return {
    // ...
    findById: getCarById
    // ...
  };
});

โมเดลระดับโลก

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

ในบางกรณีวิธีการบางอย่างจำเป็นต้องมีการเข้าถึงทั่วโลกภายในแอปพลิเคชัน เพื่อให้เป็นไปได้คุณสามารถกำหนดคุณสมบัติ ' common ' ใน$ rootScopeและผูกเข้ากับcommonModelระหว่างการบูทแอปพลิเคชัน:

angular.module('app', ['app.common'])
.config(...)
.run(['$rootScope', 'commonModel', function ($rootScope, commonModel) {
  $rootScope.common = 'commonModel';
}]);

วิธีการทั่วโลกทั้งหมดของคุณจะอาศัยอยู่ในพื้นที่ ' ทั่วไป ' นี้เป็นชนิดของบางnamespace

แต่ไม่ได้กำหนดวิธีการใด ๆ โดยตรงใน$ rootScope ของคุณ สิ่งนี้สามารถนำไปสู่พฤติกรรมที่ไม่คาดคิดเมื่อใช้กับคำสั่ง ngModel ภายในขอบเขตมุมมองของคุณซึ่งโดยทั่วไปแล้วจะทำให้การกำหนดขอบเขตและนำไปสู่วิธีการในการแก้ไขปัญหา


ทรัพยากร

ทรัพยากรช่วยให้คุณโต้ตอบกับที่แตกต่างกันแหล่งข้อมูล

ควรจะดำเนินการโดยใช้เดียวรับผิดชอบหลักการ

โดยเฉพาะอย่างยิ่งมันเป็นพร็อกซีที่นำกลับมาใช้ใหม่เพื่อจุดสิ้นสุด HTTP / JSON

ทรัพยากรจะถูกฉีดในรูปแบบและให้โอกาสในการส่ง / ดึงข้อมูล

การใช้ทรัพยากร

โรงงานซึ่งสร้างวัตถุทรัพยากรที่ให้คุณโต้ตอบกับแหล่งข้อมูลฝั่งเซิร์ฟเวอร์ที่สงบ

วัตถุทรัพยากรที่ส่งคืนมีวิธีการดำเนินการที่ให้พฤติกรรมระดับสูงโดยไม่จำเป็นต้องโต้ตอบกับบริการ $ http ระดับต่ำ


บริการ

ทั้งรูปแบบและทรัพยากรที่มีให้บริการ

บริการนั้นไม่ได้มีการเชื่อมโยงกันและมีการใช้งานร่วมกันอย่างหลวม ๆ

บริการเป็นคุณสมบัติที่ Angular นำเสนอให้กับเว็บแอปฝั่งไคลเอ็นต์จากฝั่งเซิร์ฟเวอร์ซึ่งเป็นบริการที่ใช้กันมานาน

บริการในแอพเชิงมุมเป็นวัตถุทดแทนที่เชื่อมต่อกันโดยใช้การฉีดพึ่งพา

เชิงมุมมาพร้อมกับบริการประเภทต่างๆ แต่ละคนมีกรณีการใช้งานของตัวเอง โปรดอ่านการทำความเข้าใจประเภทบริการสำหรับรายละเอียด

ลองพิจารณาหลักการสำคัญของสถาปัตยกรรมบริการในแอปพลิเคชันของคุณ

โดยทั่วไปตามอภิธานศัพท์บริการเว็บ :

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


โครงสร้างฝั่งไคลเอ็นต์

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

พยายามกำหนดโมดูลขึ้นอยู่กับคุณสมบัติ / ฟังก์ชั่นหรือมุมมองไม่ใช่ตามประเภท ดูการนำเสนอของ Miskoสำหรับรายละเอียด

ส่วนประกอบโมดูลอาจถูกจัดกลุ่มตามอัตภาพตามประเภทเช่นตัวควบคุมรุ่นมุมมองตัวกรองคำสั่ง ฯลฯ

แต่โมดูลตัวเองยังคงนำมาใช้ใหม่ , โอนเปลี่ยนมือได้และทดสอบ

นอกจากนี้ยังง่ายสำหรับนักพัฒนาในการค้นหาบางส่วนของรหัสและการอ้างอิงทั้งหมด

โปรดดูที่องค์การรหัสใน AngularJS ขนาดใหญ่และการประยุกต์ใช้งานจาวาสคริปต์เพื่อดูรายละเอียด

ตัวอย่างของโครงสร้างโฟลเดอร์ :

|-- src/
|   |-- app/
|   |   |-- app.js
|   |   |-- home/
|   |   |   |-- home.js
|   |   |   |-- homeCtrl.js
|   |   |   |-- home.spec.js
|   |   |   |-- home.tpl.html
|   |   |   |-- home.less
|   |   |-- user/
|   |   |   |-- user.js
|   |   |   |-- userCtrl.js
|   |   |   |-- userModel.js
|   |   |   |-- userResource.js
|   |   |   |-- user.spec.js
|   |   |   |-- user.tpl.html
|   |   |   |-- user.less
|   |   |   |-- create/
|   |   |   |   |-- create.js
|   |   |   |   |-- createCtrl.js
|   |   |   |   |-- create.tpl.html
|   |-- common/
|   |   |-- authentication/
|   |   |   |-- authentication.js
|   |   |   |-- authenticationModel.js
|   |   |   |-- authenticationService.js
|   |-- assets/
|   |   |-- images/
|   |   |   |-- logo.png
|   |   |   |-- user/
|   |   |   |   |-- user-icon.png
|   |   |   |   |-- user-default-avatar.png
|   |-- index.html

ตัวอย่างที่ดีของการสร้างแอปพลิเคชันเชิงมุมจะดำเนินการโดยangular-app - https://github.com/angular-app/angular-app/tree/master/client/src

นี่คือการพิจารณาโดยผู้สร้างแอปพลิเคชันที่ทันสมัย ​​- https://github.com/yeoman/generator-angular/issues/109


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

3
ฉันจะบอกว่า - รักษา Controller เป็น View Model
Artem Platonov

1
+1 คำแนะนำดีๆที่นี่! 2. น่าเสียดายที่ตัวอย่างsearchModelไม่ปฏิบัติตามคำแนะนำการใช้งานซ้ำ มันจะดีกว่าการนำเข้าค่าคงที่ผ่านconstantบริการ 3. คำอธิบายอะไรที่นี่หมายถึงอะไร?:Try to avoid having a factory that returns a new able function
Dmitri Zaitsev

1
นอกจากนี้การเขียนทับprototypeคุณสมบัติของวัตถุทำลายมรดก แต่สามารถใช้แทนได้Car.prototype.save = ...
Dmitri Zaitsev

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

46

ฉันเชื่อว่าการที่อิกอร์ใช้เรื่องนี้อย่างที่เห็นในใบเสนอราคาที่คุณให้ไว้เป็นเพียงยอดภูเขาน้ำแข็งของปัญหาที่ยิ่งใหญ่กว่า

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

ลองทำแบบนี้ทีละนิด

แนวทางปฏิบัติ

เข้าชม

ภายในบริบท Angular มุมมองคือ DOM แนวทางคือ:

ทำ:

  • ตัวแปรขอบเขตปัจจุบัน (อ่านอย่างเดียว)
  • เรียกตัวควบคุมสำหรับการดำเนินการ

ไม่ได้:

  • ใส่ตรรกะใด ๆ

นี่เป็นสิ่งที่ดึงดูดสั้นและไม่เป็นอันตราย:

ng-click="collapsed = !collapsed"

มันค่อนข้างบ่งบอกถึงนักพัฒนาที่ตอนนี้เข้าใจว่าระบบทำงานอย่างไรพวกเขาต้องการตรวจสอบทั้งไฟล์ Javascript และไฟล์ HTML

ตัวควบคุม

ทำ:

  • ผูกมุมมองกับ 'รุ่น' โดยการวางข้อมูลลงในขอบเขต
  • ตอบสนองต่อการกระทำของผู้ใช้
  • จัดการกับตรรกะการนำเสนอ

ไม่ได้:

  • จัดการกับตรรกะทางธุรกิจใด ๆ

เหตุผลสำหรับแนวทางสุดท้ายคือผู้ควบคุมเป็นพี่น้องสตรีที่มองไม่ใช่หน่วยงาน หรือนำมาใช้ซ้ำได้

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

แน่นอนว่าบางครั้งการดูจะแสดงถึงเอนทิตี แต่เป็นกรณีที่ค่อนข้างเจาะจง

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

เช่นตัวควบคุมในเชิงมุมเป็นจริงมากขึ้นของการนำเสนอรุ่นหรือMVVM

ถ้าผู้ควบคุมไม่ควรจัดการกับตรรกะทางธุรกิจใครควรทำ

แบบจำลองคืออะไร

รูปแบบลูกค้าของคุณมักจะเป็นบางส่วนและเก่า

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

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

แบบจำลองที่แท้จริงต้องคงอยู่

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

ผลที่ตามมา

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

ภายในบริบทของไคลเอนต์อาจใช้ตัวพิมพ์เล็กMดังนั้นจึงเป็นmVC , mVPและmVVmจริงๆ เรื่องใหญ่Mสำหรับเซิร์ฟเวอร์

ตรรกะทางธุรกิจ

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

  • ตรรกะของโดเมน - กฎขององค์กรธุรกิจหรือตรรกะที่ไม่ขึ้นอยู่กับแอปพลิเคชัน ตัวอย่างเช่นให้โมเดลด้วยfirstNameและsirNameคุณสมบัติ getter like getFullName()สามารถถูกพิจารณาเป็นแอ็พพลิเคชันอิสระ
  • แอปพลิเคชันตรรกะ - กฎของแอปพลิเคชันทางธุรกิจซึ่งเป็นแอปพลิเคชันเฉพาะ ตัวอย่างเช่นการตรวจสอบข้อผิดพลาดและการจัดการ

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

คำถามยังคงอยู่ - คุณโยนมันไว้ในแอปพลิเคชันเชิงมุมที่ไหน

สถาปัตยกรรม 3 vs 4 layer

กรอบ MVW เหล่านี้ทั้งหมดใช้ 3 เลเยอร์:

สามวงกลม  ด้านใน - แบบจำลอง, ตัวควบคุมกลาง, มุมมองด้านนอก

แต่มีปัญหาพื้นฐานสองประการเกี่ยวกับเรื่องนี้เมื่อพูดถึงลูกค้า:

  • โมเดลเป็นบางส่วนเก่าและไม่คงอยู่
  • ไม่มีที่สำหรับใส่ตรรกะของแอปพลิเคชัน

ทางเลือกสำหรับกลยุทธ์นี้คือกลยุทธ์เลเยอร์ 4 :

4 แวดวงจากภายในสู่ภายนอก - กฏทางธุรกิจขององค์กร, กฏทางธุรกิจของแอปพลิเคชัน, อะแดปเตอร์อินเตอร์เฟส, กรอบงานและไดรเวอร์

ข้อตกลงจริงที่นี่คือเลเยอร์กฎทางธุรกิจของแอปพลิเคชัน (ใช้กรณี) ซึ่งมักจะผิดพลาดกับลูกค้า

ชั้นนี้เป็นตระหนักโดย interactors (ลุงบ๊อบ) ซึ่งเป็นสิ่งที่สวยมากมาร์ตินฟาวเลอร์เรียกการดำเนินการบริการชั้นสคริปต์

ตัวอย่างคอนกรีต

พิจารณาเว็บแอปพลิเคชันต่อไปนี้:

  • แอปพลิเคชั่นแสดงรายชื่อผู้ใช้
  • ผู้ใช้คลิก 'เพิ่มผู้ใช้'
  • แบบจำลองจะเปิดขึ้นพร้อมกับแบบฟอร์มเพื่อกรอกรายละเอียดผู้ใช้
  • ผู้ใช้กรอกแบบฟอร์มและกดส่ง

บางสิ่งที่ควรเกิดขึ้นตอนนี้:

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

เราจะโยนทั้งหมดนี้ไปที่ไหน

หากสถาปัตยกรรมของคุณเกี่ยวข้องกับคอนโทรลเลอร์ที่เรียกใช้$resourceสิ่งนี้จะเกิดขึ้นภายในคอนโทรลเลอร์ แต่มีกลยุทธ์ที่ดีกว่าคือ

ทางออกที่นำเสนอ

แผนภาพต่อไปนี้แสดงให้เห็นว่าปัญหาข้างต้นสามารถแก้ไขได้โดยการเพิ่มชั้นตรรกะแอปพลิเคชันอื่นใน Angular clients:

4 กล่อง - DOM ชี้ไปที่ตัวควบคุมซึ่งชี้ไปยังตรรกะของแอปพลิเคชันซึ่งชี้ไปที่ $ ทรัพยากร

ดังนั้นเราจึงเพิ่มเลเยอร์ระหว่างตัวควบคุมไปยัง $ resource, เลเยอร์นี้ (เรียกว่ามันเป็นตัวตอบโต้ ):

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

ดังนั้นด้วยความต้องการของตัวอย่างที่เป็นรูปธรรมด้านบน:

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

ดังนั้น AngularJS จึงถูกกำหนด MVW (โดยที่ W เป็นของอะไรก็ตาม) เนื่องจากฉันสามารถเลือกให้มี Controller (ด้วยตรรกะทางธุรกิจทั้งหมดในนั้น) หรือ View Model / Presenter (ไม่มีตรรกะทางธุรกิจ แต่เพียงบางโค้ดเพื่อเติมมุมมอง) ด้วย BL ใน บริการแยกต่างหาก ฉันถูกไหม?
BAD_SEED

คำตอบที่ดีที่สุด คุณมีตัวอย่างจริงใน GitHub ของแอพเชิงมุม 4 ชั้นหรือไม่?
RPallas

1
@RPallas ไม่ฉันไม่ (หวังว่าฉันจะมีเวลานี้) ขณะนี้เรากำลังลองใช้สถาปัตยกรรมที่ 'แอปพลิเคชันตรรกะ' เป็นเพียงผู้โต้ตอบ ตัวแก้ไขระหว่างมันกับคอนโทรลเลอร์และโมเดลมุมมองที่มีลอจิกมุมมองบางส่วน เรายังคงทำการทดลองอยู่ดังนั้นไม่ใช่ข้อดีหรือข้อเสีย 100% แต่เมื่อเสร็จแล้วฉันหวังว่าจะเขียนบล็อกที่ไหนสักแห่ง
Izhaki

1
@heringer โดยทั่วไปเราแนะนำรุ่น - โครงสร้าง OOP ที่เป็นตัวแทนของโดเมน มันเป็นแบบจำลองเหล่านี้ที่สื่อสารกับทรัพยากรไม่ใช่ตัวควบคุม พวกเขาแค็ปซูลอจิกโดเมน ตัวควบคุมเรียกรุ่นซึ่งจะเปิดทรัพยากรการโทร
Izhaki

1
@ alex440 ไม่แม้ว่าจะเป็นเวลาสองเดือนแล้วที่การโพสต์บล็อกอย่างจริงจังเกี่ยวกับหัวข้อนี้อยู่ที่ปลายนิ้วของฉัน Xmas กำลังจะมา - อาจเป็นไปได้
Izhaki

5

ปัญหาเล็กน้อยเมื่อเทียบกับคำแนะนำที่ดีในคำตอบของ Artem แต่ในแง่ของความสามารถในการอ่านรหัสฉันพบว่าดีที่สุดในการกำหนด API อย่างสมบูรณ์ภายในreturnวัตถุเพื่อลดการย้อนกลับไปมาในรหัสเพื่อดูตัวแปรที่มีตัวแปร

angular.module('myModule', [])
// or .constant instead of .value
.value('myConfig', {
  var1: value1,
  var2: value2
  ...
})
.factory('myFactory', function(myConfig) {
  ...preliminary work with myConfig...
  return {
    // comments
    myAPIproperty1: ...,
    ...
    myAPImethod1: function(arg1, ...) {
    ...
    }
  }
});

หากreturnวัตถุนั้นดู "แออัดเกินไป" นั่นเป็นสัญญาณว่าบริการกำลังทำมากเกินไป


0

AngularJS doest ไม่ใช้ MVC ในลักษณะดั้งเดิม แต่ใช้บางสิ่งที่ใกล้เคียงกับ MVVM (Model-View-ViewModel), ViewModel ยังสามารถเรียกได้ว่าเป็นตัวประสาน (ในกรณีเชิงมุมสามารถเป็น $ scope) โมเดล -> ดังที่เราทราบโมเดลในเชิงมุมอาจเป็นเพียงวัตถุ JS เก่าธรรมดาหรือข้อมูลในแอปพลิเคชันของเรา

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

ViewModel -> ViewModel จริง ๆ แล้วเป็นตัวเชื่อมโยง / บริดจ์ระหว่างมุมมองและโมเดลของคุณในกรณี angularJS เป็น $ scope เพื่อเริ่มต้นและเพิ่มขอบเขต $ ที่เราใช้ Controller

หากฉันต้องการสรุปคำตอบ: ในขอบเขต $ application ของ angularJS มีการอ้างอิงถึงข้อมูลคอนโทรลเลอร์จะควบคุมพฤติกรรมและ View จัดการโครงร่างโดยการโต้ตอบกับคอนโทรลเลอร์เพื่อให้ทำงานได้ตามนั้น


-1

เพื่อให้ชัดเจนเกี่ยวกับคำถาม Angular ใช้รูปแบบการออกแบบที่แตกต่างซึ่งเราได้พบแล้วในการเขียนโปรแกรมปกติของเรา 1) เมื่อเราลงทะเบียนผู้ควบคุมหรือคำสั่งโรงงานบริการและอื่น ๆ ที่เกี่ยวข้องกับโมดูลของเรา นี่มันซ่อนข้อมูลจากอวกาศทั่วโลก ซึ่งเป็นรูปแบบโมดูล 2) เมื่อใช้การตรวจสอบสกปรกสำหรับการเปรียบเทียบตัวแปรขอบเขตเชิงมุมที่นี่จะใช้รูปแบบการสังเกตการณ์ 3) ขอบเขตเด็กผู้ปกครองทั้งหมดในตัวควบคุมของเราใช้รูปแบบต้นแบบ 4) ในกรณีที่มีการฉีดบริการที่จะใช้รูปแบบโรงงาน

โดยรวมแล้วใช้รูปแบบการออกแบบที่เป็นที่รู้จักแตกต่างกันเพื่อแก้ปัญหา

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