2012-09-05 14 views
8

मैं डेमेटर के कानून का पालन करने की कोशिश कर रहा हूं (http://en.wikipedia.org/wiki/Law_of_Demeter, http://misko.hevery.com/code-reviewers-guide/flaw-digging-into-collaborators/ देखें) क्योंकि मैं लाभ देख सकता हूं, हालांकि डोमेन ऑब्जेक्ट्स की बात आने पर मैं थोड़ा फंस गया हूं।डेमेटर का कानून - डेटा ऑब्जेक्ट्स

डोमेन ऑब्जेक्ट्स स्वाभाविक रूप से एक श्रृंखला होती है और कभी-कभी संपूर्ण श्रृंखला के बारे में जानकारी प्रदर्शित करना आवश्यक होता है।

उदाहरण के लिए, एक शॉपिंग टोकरी:

प्रत्येक आदेश एक उपयोगकर्ता, वितरण की जानकारी और प्रत्येक आदेश आइटम एक उत्पाद और मात्रा प्रत्येक उत्पाद का एक नाम और मूल्य है शामिल मदों की एक सूची में शामिल है। प्रत्येक उपयोगकर्ता का नाम और पता

ऑर्डर जानकारी प्रदर्शित करने वाला कोड ऑर्डर, उपयोगकर्ता और उत्पादों के बारे में सभी जानकारी का उपयोग करना है।

निश्चित रूप से ऑर्डर ऑब्जेक्ट के माध्यम से यह जानकारी प्राप्त करने के लिए यह बेहतर और अधिक पुन: प्रयोज्य है। "order.user.address.city" कुछ कोडों के मुकाबले ऊपर सूचीबद्ध सभी ऑब्जेक्ट्स के लिए प्रश्न पूछने के लिए उच्चतर कोड को अलग से पास कर दें?

कोई टिप्पणी/सुझाव/सुझाव स्वागत है!

+0

आपका प्रश्न क्या है? – hakre

+1

मुझे पता है कि यहां कोई विशिष्ट प्रश्न नहीं है ... मैंने बक्षीस रखा क्योंकि वहां * हो सकता है और विषय वस्तु पर चर्चा करने लायक है। शायद ओपी सवाल को थोड़ा सा स्पष्ट कर सकता है? – rdlowrey

+0

@ आपकी समस्या यह है कि आप क्लास ऑर्डर में क्लास उपयोगकर्ता, क्लास इन्फो, और उत्पाद कक्षाओं की एक सरणी इंजेक्शन पसंद नहीं करते हैं? वे अपेक्षाकृत स्वतंत्र हैं हालांकि –

उत्तर

7

एक समस्या यह है कि उच्च क्रम निर्भरता कक्षा के बाहर कोड की संरचना "में शामिल किया हुआ" हो जाता है।

आदर्श रूप से, जब आप अपनी कक्षा को दोबारा प्रतिक्रिया देते हैं, तो आपके "मजबूर परिवर्तन" को कक्षा के तरीकों तक सीमित किया जाना चाहिए। जब आपके पास क्लाइंट कोड में एकाधिक जंजीर संदर्भ होते हैं, तो आपके कोड के अन्य स्थानों में परिवर्तन करने के लिए ड्राइव को रीफैक्टर करना।

एक उदाहरण पर विचार करें: मान लें कि आप एक OrderPlacingParty साथ User को बदलने के लिए, एक अमूर्त उपयोगकर्ताओं, कंपनियों, और इलेक्ट्रॉनिक एजेंटों कि एक आदेश जगह कर सकते हैं encapsulating चाहते हैं। यह रिफैक्टरिंग तुरंत कई समस्याएं प्रस्तुत करता है:

  • User संपत्ति कुछ और कहा जाएगा, और यह एक अलग प्रकार
  • होगा नई संपत्ति एक address है कि मामलों में city नहीं हो सकता है जब आदेश दे दिए जाने एक इलेक्ट्रॉनिक एजेंट द्वारा
  • मानव User आदेश से जुड़ा हुआ है (मान लीजिए कि आपके सिस्टम को कानूनी कारणों से एक की आवश्यकता है) अप्रत्यक्ष रूप से आदेश से संबंधित हो सकता है - उदाहरण के लिए, परिभाषित जाने वाले व्यक्ति को परिभाषित करने के लिए OrderPlacingParty

इन समस्याओं के समाधान के क्रम प्रस्तुति तर्क सब कुछ है कि यह सीधे की जरूरत है, बल्कि यह की तुलना में पारित करने के लिए किया जाएगा "समझ" वस्तुओं में। इस तरह से पारित कर दिया की संरचना क्या आप परिवर्तनों को स्थानीय बनाना करने में सक्षम होगा संभावित रूप से स्थिर होने वाले अन्य कोड में परिवर्तनों को फैलाने के बिना, कोड को दोबारा सुधारने के लिए।

interface OrderPresenter { 
    void present(Order order, User user, Address address); 
} 
interface Address { 
    ... 
} 
class PhysicalAddress implements Address { 
    public String getStreetNumber(); 
    public String getCity(); 
    public String getState(); 
    public String getCountry(); 
} 
class ElectronicAddress implements Address { 
    public URL getUrl(); 
} 
interface OrderPlacingParty { 
    Address getAddress(); 
} 
interface Order { 
    OrderPlacingParty getParty(); 
} 
class User implements OrderPlacingParty { 
} 
class Company implements OrderPlacingParty { 
    public User getResponsibleUser(); 
} 
class ElectronicAgent implements OrderPlacingParty { 
    public User getResponsibleUser(); 
} 
+0

धन्यवाद! यही समस्या है कि मैं तर्काडाल के जवाब में मेरी टिप्पणियों में व्याख्या करने की कोशिश कर रहा था लेकिन आप इसे सक्षम करने से कहीं अधिक स्पष्ट रूप से डाल दिया। यह निश्चित रूप से एक बेहतर समाधान है! मैं इसे अभ्यास में रखने की कोशिश करूंगा, सही दिशा में मुझे इंगित करने के लिए धन्यवाद! –

1

आप सही हैं और आप करेंगे सबसे अधिक संभावना मॉडल अपने मूल्य इस

class Order { 
    User user; 
} 

class User { 
    Address shippingAddress; 
    Address deliveryAddress; 
} 

class Address { 
    String city; 
    ... 
} 

की तरह कुछ वस्तुओं जब आप पर विचार कैसे आप एक डेटाबेस के लिए इस डेटा बना रहेगा शुरू (जैसे ORM) आप के बारे में सोचना शुरू करते हैं प्रदर्शन। आलसी बनाम आलसी लोडिंग व्यापार बंद सोचो।

+0

धन्यवाद! हां, मैंने यही सोचा। अन्य मुद्दा जो यहां स्पष्ट है, वह encapsulation है। ऑर्डर ऑब्जेक्ट को वास्तव में उपयोगकर्ता या उत्पाद के कार्यान्वयन विवरणों को नहीं जानना चाहिए ... लेकिन एप्लिकेशन को जमीन को कहीं छूना है। क्या वस्तुओं को मुख्य रूप से डेटा संरचनाओं के रूप में सेवा करने के लिए अलग-अलग इलाज किया जाना चाहिए? –

+0

इस तरह से सभी * कक्षाओं (डेटा और कार्यात्मक) तक पहुंचने के लिए यह अच्छा डिजाइन है। फैंसी कंप्यूटर टॉक में, इसका मतलब निम्न [साइक्लोमैटिक कॉम्प्लेक्सिटी] (http://en.wikipedia.org/wiki/Cyclomatic_complexity) और उच्च स्तर [Cohesion] (http://en.wikipedia.org) के साथ कक्षाएं रखने का प्रयास कर रहा है/विकी/Cohesion_% 28computer_science% 29) – Brad

1

आम तौर पर मैं डेमेटर के कानून का पालन करता हूं क्योंकि यह कम दायरे में परिवर्तनों को रखने में मदद करता है, ताकि एक नई आवश्यकता या बग फिक्स आपके सिस्टम पर फैल न सके। ऐसे अन्य डिज़ाइन दिशानिर्देश हैं जो इस दिशा में सहायता करते हैं, उदा। this article में सूचीबद्ध हैं। ऐसा कहकर, मैं डेमेटर (साथ ही डिजाइन पैटर्न और अन्य समान सामान) के रूप में उपयोगी डिजाइन दिशानिर्देशों के रूप में विचार करता हूं जिनके व्यापार-बंद होते हैं और यदि आप इसका न्याय करते हैं तो आप उन्हें तोड़ सकते हैं। उदाहरण के लिए मैं आम तौर पर test private methods नहीं करता, मुख्य रूप से क्योंकि यह fragile tests बनाता है। हालांकि, कुछ विशेष मामलों में मैंने एक ऑब्जेक्ट निजी विधि का परीक्षण किया क्योंकि मैंने इसे अपने ऐप में बहुत महत्वपूर्ण माना था, यह जानकर कि ऑब्जेक्ट के कार्यान्वयन में बदलाव होने पर यह विशेष परीक्षण परिवर्तनों के अधीन होगा। बेशक उन मामलों में आपको अतिरिक्त सावधान रहना होगा और अन्य डेवलपर्स के लिए और अधिक दस्तावेज छोड़ना होगा कि आप यह क्यों कर रहे हैं। लेकिन, अंत में, आपको अपने अच्छे फैसले का उपयोग करना होगा :)।

अब, मूल प्रश्न पर वापस जाएं। जहां तक ​​मैं आपकी समस्या को समझता हूं, वहां एक ऑब्जेक्ट के लिए (वेब?) जीयूआई लिख रहा है जो ऑब्जेक्ट्स के ग्राफ की जड़ है जिसे संदेश श्रृंखलाओं के माध्यम से एक्सेस किया जा सकता है। उस मामले के लिए मैं आपके मॉडल के प्रत्येक ऑब्जेक्ट के लिए व्यू घटक निर्दिष्ट करके, जीयूआई को उसी तरीके से मॉड्यूलर करता हूं जैसा आपने अपना मॉडल बनाया है। नतीजतन आपके पास OrderView, AddressView आदि जैसे वर्ग होंगे, जो जानते हैं कि उनके संबंधित मॉडल के लिए HTML कैसे बनाएं। फिर आप उन अंतिम विचारों को लिखने के लिए उन विचारों को लिख सकते हैं, या तो उन्हें जिम्मेदारी सौंपकर (उदा। OrderViewAddressView बनाता है) या Mediator जो उन्हें लिखने और उन्हें अपने मॉडल से जोड़ने का ख्याल रखता है।पहले दृष्टिकोण का एक उदाहरण के रूप में आप कुछ इस तरह हो सकता है (मैं उदाहरण के लिए PHP का उपयोग करेंगे, मैं कौन-सी भाषा का उपयोग कर रहे पता नहीं):

class ShoppingBasket 
{ 
    protected $orders; 
    protected $id; 

    public function getOrders(){...} 
    public function getId(){...} 
} 

class Order 
{ 
    protected $user; 

    public function getUser(){...} 
} 

class User 
{ 
    protected $address; 

    public function getAddress(){...} 
} 

और फिर विचार:

class ShoppingBasketView 
{ 
    protected $basket; 
    protected $orderViews; 

    public function __construct($basket) 
    { 
    $this->basket = $basket; 
    $this->orederViews = array(); 
    foreach ($basket->getOrders() as $order) 
    { 
     $this->orederViews[] = new OrderView($order); 
    } 
    } 

    public function render() 
    { 
    $contents = $this->renderBasketDetails(); 
    $contents .= $this->renderOrders();  
    return $contents; 
    } 

    protected function renderBasketDetails() 
    { 
    //Return the HTML representing the basket details 
    return '<H1>Shopping basket (id=' . $this->basket->getId() .')</H1>'; 
    } 

    protected function renderOrders() 
    { 
    $contents = '<div id="orders">'; 
    foreach ($this->orderViews as $orderView) 
    { 
     $contents .= orderViews->render(); 
    } 
    $contents .= '</div>'; 
    return $contents; 
    } 
} 

class OrderView 
{ 
//The same basic pattern; store your domain model object 
//and create the related sub-views 

    public function render() 
    { 
    $contents = $this->renderOrderDetails(); 
    $contents .= $this->renderSubViews(); 
    return $contents; 
    } 

    protected function renderOrderDetails() 
    { 
    //Return the HTML representing the order details 
    } 

    protected function renderOrders() 
    { 
    //Return the HTML representing the subviews by 
    //forwarding the render() message 
    } 
} 

और अपने view.php में आप की तरह कुछ करना होगा:

$basket = //Get the basket based on the session credentials 
$view = new ShoppingBasketView($basket); 
echo $view->render(); 

यह दृष्टिकोण एक घटक मॉडल है, जहां विचारों composable घटक के रूप में इलाज कर रहे हैं पर आधारित है। इस स्कीमा में आप ऑब्जेक्ट की सीमाओं का सम्मान करते हैं और प्रत्येक दृश्य की एक ज़िम्मेदारी होती है।

संपादित करें (जोड़ा ओपी टिप्पणी के आधार पर)

मैं तुम्हें टोकरी आईडी, आदेश की तारीख और उपयोगकर्ता नाम में प्रस्तुत करने की जरूरत है subviews में और है कि विचारों के आयोजन का कोई रास्ता नहीं है कि वहाँ मान लेंगे एक लाइन जैसा कि मैंने टिप्पणी में कहा था, उस मामले के लिए मैं यह सुनिश्चित कर दूंगा कि "खराब" पहुंच एक एकल, अच्छी तरह से प्रलेखित स्थान पर की जाती है, जिससे दृश्य इस बात से अनजान हो जाता है।

class MixedView 
{ 
    protected $basketId; 
    protected $orderDate; 
    protected $userName; 

    public function __construct($basketId, $orderDate, $userName) 
    { 
    //Set internal state 
    } 


    public function render() 
    { 
    return '<H2>' . $this->userName . "'s basket (" . $this->basketId . ")<H2> " . 
      '<p>Last order placed on: ' . $this->orderDate. '</p>'; 
    } 
} 

class ViewBuilder 
{ 
    protected $basket; 

    public function __construct($basket) 
    { 
    $this->basket = $basket; 
    } 

    public function getView() 
    { 
    $basketId = $this->basket->getID(); 
    $orderDate = $this->basket->getLastOrder()->getDate(); 
    $userName = $this->basket->getUser()->getName(); 
    return new MixedView($basketId, $orderDate, $userName); 
    } 
} 

अगर बाद में आप अपने डोमेन के मॉडल को पुनर्व्यवस्थित करने और अपने ShoppingBasket वर्ग अब getUser() संदेश लागू नहीं कर सकते तो आप अपने आवेदन में एक बिंदु को बदलने कि परिवर्तन सब आपके सिस्टम में फैले होने से बचने करना होगा।

HTH

+0

यह निश्चित रूप से एक अच्छा दृष्टिकोण है जब दृश्य वास्तव में विशिष्ट हो सकता है। लेकिन "हाय $ नाम" के रूप में कुछ छोटे के बारे में क्या, आपका अंतिम आदेश $ LastOrderDate पर रखा गया था या किसी अन्य मामले में जहां डेटासेट मिश्रित होते हैं। इस मामले में, आपको दो सेट, ग्राहक और उनके नवीनतम आदेश से डेटा चाहिए। और हालांकि मैं इस दृष्टिकोण के लिए कुछ लाभ देख सकता हूं, यह संभावित रूप से न्यूनतम लाभ के लिए बहुत अधिक कोड है।यह निश्चित रूप से एक बेहतर समाधान की तरह लगता है लेकिन मुझे यकीन है कि कहीं एक मध्यम जमीन है। –

+0

यदि यह समस्या थी (वस्तुओं की एक श्रृंखला में एक एकल विशेषता का उपयोग किया जा सकता है) मैं आंतरिक रूप से प्रतिनिधि और अंततः डेमेटर के कानून पर लचीला होगा (जैसा कि मैंने उत्तर में कहा था)। इसलिए, 'उपयोगकर्ता' 'getName()' और 'getLatestOrder()' लागू करेगा और आपके विचार में आप इसकी तारीख के लिए आदेश मांगेंगे। अगर यह आपके सिस्टम में एक बार का मामला था तो मैं ज्यादा चिंता नहीं करता। मिश्रित डेटा मामले के लिए, यदि आप इसे सबव्यूज़ में फैक्टरिंग का कोई संभावित तरीका नहीं देखते हैं, तो आपको यह सुनिश्चित करना चाहिए कि आप एक एकल, अच्छी तरह से प्रलेखित स्थान में डेमेटर के कानून को तोड़ दें (मैं इसका उदाहरण देने के लिए उत्तर संपादित करूंगा) –

2

मुझे लगता है, जब चेनिंग कुछ संपत्ति का उपयोग करने के लिए किया जाता है, यह दो (या कम से कम दो) अलग-अलग स्थिति में किया जाता है। एक ऐसा मामला है जिसका आपने उल्लेख किया है, उदाहरण के लिए, आपके प्रेजेंटेशन मॉड्यूल में, आपके पास ऑर्डर ऑब्जेक्ट है और आप केवल मालिक के/उपयोगकर्ता का पता, या शहर जैसे विवरण प्रदर्शित करना चाहते हैं। उस स्थिति में, मुझे लगता है कि यदि आप ऐसा करते हैं तो यह बहुत अधिक समस्या नहीं है। क्यूं कर? चूंकि आप किसी भी व्यवसाय तर्क तक पहुंचने वाली संपत्ति पर नहीं कर रहे हैं, जो (संभावित रूप से) तंग युग्मन का कारण बन सकता है।

लेकिन, यदि आप एक्सेस की गई संपत्ति पर कुछ तर्क करने के उद्देश्य से ऐसी श्रृंखला का उपयोग करते हैं तो चीजें अलग होती हैं। उदाहरण के लिए, यदि आपके पास है,

String city = order.user.address.city; 
... 
order.user.address.city = "New York"; 

यह समस्याग्रस्त है। चूंकि, यह तर्क लक्षित विशेषता - शहर के करीब एक मॉड्यूल में अधिक उचित रूप से किया जाना चाहिए। जैसे, उस स्थान पर जहां पता ऑब्जेक्ट पहली जगह में बनाया गया है, या यदि ऐसा नहीं है, कम से कम जब उपयोगकर्ता ऑब्जेक्ट बनाया गया है (यदि कहते हैं कि उपयोगकर्ता इकाई है और मान प्रकार को संबोधित करता है)। लेकिन, अगर यह उससे कहीं अधिक हो जाता है, जितना आगे जाता है, उतना ही अधिक अजीब और समस्याग्रस्त हो जाता है। क्योंकि स्रोत और लक्ष्य के बीच बहुत से मध्यस्थ शामिल हैं।

इस प्रकार, Demeter के कानून के अनुसार, यदि आप एक कक्षा में "शहर" विशेषता के बारे में कुछ तर्क प्रदर्शन कर रहे हैं, OrderAssmebler, जो order.user.address की तरह एक श्रृंखला में शहर विशेषता तक पहुँचता है का कहना है। शहर, तो आपको इस तर्क को लक्ष्य के करीब किसी स्थान/मॉड्यूल पर ले जाने के बारे में सोचना चाहिए।

0

डेमेटर का कानून गुणों को कॉल करने के बारे में है, गुणों/फ़ील्ड तक नहीं पहुंच रहा है। मुझे पता है तकनीकी रूप से गुण विधियां हैं, लेकिन तार्किक रूप से वे डेटा होने के लिए हैं। तो, order.user.address.city का आपका उदाहरण मुझे ठीक लगता है।

यह लेख दिलचस्प आगे पढ़ने है: श्रृंखलित संदर्भ, जैसे order.user.address.city उपयोग करने के साथ http://haacked.com/archive/2009/07/13/law-of-demeter-dot-counting.aspx

+0

लेकिन , encapsulation के माध्यम से निर्भरताओं की श्रृंखला से बचने के लिए डेमेटर के कानून की भावना है। केवल निकटतम पड़ोसी encapsulation के एपीआई को जानकर बढ़ी है क्योंकि निर्भरता को प्रतिस्थापित किया जा सकता है, उदा। कोड को तोड़ने के बिना, परीक्षण में एक नकली वस्तु के साथ। उस उदाहरण में, उपयोगकर्ता को नकली उपयोगकर्ता ऑब्जेक्ट के साथ बदलना चीजों को तोड़ने का कारण बनता है या कम से कम इसे प्राप्त करने के लिए महत्वपूर्ण मात्रा में नकली सेट की आवश्यकता होती है, जो इस तरह की याद आती है। –

+0

मान्य है, इस तरह के विचारों का परीक्षण नहीं किया जा रहा है, लेकिन परीक्षण प्रयोजनों के लिए एक वास्तविक दृश्य में नकली वस्तु को लोड करना उपयोगी है। यदि उपयोगकर्ता का एपीआई बदल दिया गया था, उदा। एक पते के बजाय एक डिलीवरी पता और शिपिंग पता दोनों रखने के लिए, चीजें अप्रत्याशित रूप से ऐसी जगहों पर टूट जाएंगी जो वास्तव में "पता" को निर्भरता के रूप में सूचीबद्ध नहीं करती हैं, जो आदर्श –

+0

@TomB हां से बहुत दूर है, इसकी भावना है डेटा छिपाने के बारे में। आपका प्रश्न स्पष्ट रूप से डेटा * दिखा रहा है * के बारे में है। दृश्य (या जो कुछ भी) संभवतः "उपयोगकर्ता", "पता", "शहर", आदि के लिए लेबल होने जा रहा है ... परिभाषा के अनुसार, यह * इस सामग्री के बारे में जानना है। मुझे इस जानकारी को सबसे आसान तरीके से प्राप्त करने में समस्या दिखाई नहीं दे रही है। – TarkaDaal

संबंधित मुद्दे