के रूप में काफी कुछ जवाब और टिप्पणियों में कहा गया है, DTOs हैं विशेष रूप से सीमाओं के पार डेटा स्थानांतरित (जैसे JSON के serializing एक वेब सेवा के माध्यम से भेजने के लिए) में, कुछ स्थितियों में उचित और उपयोगी। इस उत्तर के बाकी हिस्सों के लिए, मैं कमोबेश इसे अनदेखा करूंगा और डोमेन कक्षाओं के बारे में बात करूंगा, और वे गेटर्स और सेटरर्स को कम से कम करने के लिए कैसे डिज़ाइन किया जा सकता है, और अभी भी एक बड़े प्रोजेक्ट में उपयोगी हो सकते हैं। मैं इस बारे में भी बात नहीं करूंगा कि गेटर्स या सेटर को क्यों हटाया जाए, या ऐसा कब किया जाए, क्योंकि ये उनके अपने सवाल हैं।
एक उदाहरण के रूप में, कल्पना करें कि आपकी परियोजना शतरंज या युद्धपोत की तरह एक बोर्ड गेम है। आपके पास प्रस्तुति परत (कंसोल ऐप, वेब सेवा, जीयूआई, आदि) में इसका प्रतिनिधित्व करने के विभिन्न तरीके हो सकते हैं, लेकिन आपके पास एक मुख्य डोमेन भी है। एक वर्ग जो आपके पास हो सकता है Coordinate
, वह बोर्ड पर एक स्थिति का प्रतिनिधित्व करता है। इसे लिखने का "दुष्ट" तरीका होगा:
public class Coordinate
{
public int X {get; set;}
public int Y {get; set;}
}
(मैं जावा के बजाय संक्षिप्तता के लिए C # में कोड उदाहरण लिखने जा रहा हूं और क्योंकि मैं इससे अधिक परिचित हूं। उम्मीद है कि यह कोई समस्या नहीं है। अवधारणाएं समान हैं और अनुवाद सरल होना चाहिए।)
निष्कासन सेटर्स: अपरिवर्तनीयता
जबकि सार्वजनिक गेटर्स और सेटर दोनों संभावित रूप से समस्याग्रस्त हैं, बसने वाले दोनों की अधिक "बुराई" हैं। वे भी आमतौर पर खत्म करने के लिए आसान कर रहे हैं। प्रक्रिया एक सरल है- निर्माणकर्ता के भीतर से मूल्य निर्धारित करें। कोई भी विधि जो पहले ऑब्जेक्ट को उत्परिवर्तित करती है, उसके बजाय एक नया परिणाम वापस करना चाहिए। इसलिए:
public class Coordinate
{
public int X {get; private set;}
public int Y {get; private set;}
public Coordinate(int x, int y)
{
X = x;
Y = y;
}
}
ध्यान दें कि यह एक्स और वाई को बदलने वाले वर्ग के अन्य तरीकों के खिलाफ सुरक्षा नहीं करता है। अधिक सख्ती से अपरिवर्तनीय होने के लिए, आप readonly
( final
जावा में) उपयोग कर सकते हैं । लेकिन किसी भी तरह से- चाहे आप अपने गुणों को वास्तव में अपरिवर्तनीय बनाते हैं या बस के माध्यम से प्रत्यक्ष सार्वजनिक उत्परिवर्तन को रोकते हैं- यह आपके सार्वजनिक बसाने को दूर करने की चाल है। अधिकांश स्थितियों में, यह ठीक काम करता है।
गेटिंग रिमूवर्स, पार्ट 1: बिहेवियर के लिए डिजाइनिंग
उपरोक्त सभी बसने के लिए अच्छी तरह से और अच्छा है, लेकिन गेटर्स के संदर्भ में, हमने वास्तव में शुरू होने से पहले खुद को पैर में गोली मार ली। हमारी प्रक्रिया यह सोचने की थी कि एक समन्वय क्या है- वह डेटा जो इसका प्रतिनिधित्व करता है- और उसके चारों ओर एक वर्ग बनाता है। इसके बजाय, हमें एक समन्वय से किस व्यवहार की आवश्यकता है, इसकी शुरुआत करनी चाहिए । यह प्रक्रिया, वैसे, टीडीडी द्वारा सहायता प्राप्त है, जहां हम केवल इस तरह की कक्षाएं निकालते हैं, जब एक बार हमें उनकी आवश्यकता होती है, इसलिए हम वांछित व्यवहार से शुरू करते हैं और वहां से काम करते हैं।
तो चलिए बताते हैं कि पहली जगह जो आपने खुद को मिली Coordinate
थी वह टक्कर का पता लगाने के लिए थी: यदि आप जांचना चाहते हैं कि क्या दो टुकड़े बोर्ड पर एक ही स्थान पर हैं। यहां "बुराई" तरीका है (निर्माणकर्ता संक्षिप्तता के लिए छोड़ दिया गया है):
public class Piece
{
public Coordinate Position {get; private set;}
}
public class Coordinate
{
public int X {get; private set;}
public int Y {get; private set;}
}
//...And then, inside some class
public bool DoPiecesCollide(Piece one, Piece two)
{
return one.X == two.X && one.Y == two.Y;
}
और यहाँ अच्छा तरीका है:
public class Piece
{
private Coordinate _position;
public bool CollidesWith(Piece other)
{
return _position.Equals(other._position);
}
}
public class Coordinate
{
private readonly int _x;
private readonly int _y;
public bool Equals(Coordinate other)
{
return _x == other._x && _y == other._y;
}
}
( IEquatable
कार्यान्वयन सादगी के लिए संक्षिप्त)। मॉडलिंग डेटा के बजाय व्यवहार के लिए डिजाइन करके, हम अपने गेटर्स को हटाने में कामयाब रहे।
ध्यान दें कि यह आपके उदाहरण के लिए भी प्रासंगिक है। आप एक ORM का उपयोग कर रहे होंगे, या किसी वेबसाइट या किसी चीज़ पर ग्राहक की जानकारी प्रदर्शित कर सकते हैं, जिस स्थिति में किसी प्रकार का Customer
DTO शायद समझ में आएगा। लेकिन सिर्फ इसलिए कि आपके सिस्टम में ग्राहक शामिल हैं और वे डेटा मॉडल में प्रतिनिधित्व करते हैं, इसका मतलब यह नहीं है कि Customer
आपके डोमेन में एक वर्ग होना चाहिए । हो सकता है कि जैसा आप व्यवहार के लिए डिज़ाइन करते हैं, एक उभर जाएगा, लेकिन यदि आप गेटर्स से बचना चाहते हैं, तो एक पूर्व-खाली न बनाएं।
रिमूवल गेटर्स, भाग 2: बाहरी व्यवहार
तो ऊपर एक अच्छी शुरुआत है, लेकिन अभी या बाद में आप शायद एक स्थिति है जहाँ आप व्यवहार जो एक वर्ग है, जो किसी तरह से वर्ग की राज्य पर निर्भर करता है के साथ जुड़ा हुआ है में चलाया जाएगा, लेकिन जो नहीं है पर वर्ग। इस प्रकार का व्यवहार वह है जो आमतौर पर आपके एप्लिकेशन की सेवा परत में रहता है ।
हमारा Coordinate
उदाहरण लेते हुए , आखिरकार आप अपने गेम को उपयोगकर्ता के लिए प्रस्तुत करना चाहेंगे, और इसका मतलब स्क्रीन पर ड्राइंग करना हो सकता है। उदाहरण के लिए, आपके पास एक यूआई परियोजना हो सकती है जो Vector2
स्क्रीन पर एक बिंदु का प्रतिनिधित्व करने के लिए उपयोग करती है। लेकिन Coordinate
वर्ग के लिए स्क्रीन पर एक समन्वय से एक बिंदु तक परिवर्तित करने का प्रभार लेना अनुचित होगा- जो आपके मुख्य डोमेन में सभी प्रकार की प्रस्तुति चिंताओं को लाएगा। दुर्भाग्य से इस प्रकार की स्थिति ओओ डिजाइन में अंतर्निहित है।
पहला विकल्प , जो बहुत सामान्य रूप से चुना जाता है, बस लानत पाने वालों को बेनकाब करता है और इसके साथ नरक के लिए कहता है। इससे सादगी का लाभ होता है। लेकिन चूंकि हम गेट्स से बचने के बारे में बात कर रहे हैं, चलो तर्क के लिए कहते हैं कि हम इसे अस्वीकार करते हैं और देखते हैं कि अन्य विकल्प क्या हैं।
एक दूसरा विकल्प यह है कि .ToDTO()
अपनी कक्षा में किसी प्रकार की विधि को जोड़ा जाए । यह या इसी तरह की जरूरत वैसे भी हो सकती है, उदाहरण के लिए जब आप खेल को बचाना चाहते हैं तो आपको अपने राज्य के सभी हिस्सों पर कब्जा करने की जरूरत है। लेकिन आपकी सेवाओं के लिए ऐसा करने और सीधे गेटटर तक पहुंचने के बीच का अंतर कम या ज्यादा सौंदर्यवादी है। यह अभी भी इसे करने के लिए "बुराई" है।
एक तीसरा विकल्प - जिसे मैंने प्लोरललाइट वीडियो के एक जोड़े में ज़ोरान होर्वत द्वारा वकालत करते देखा है- विज़िटर पैटर्न के संशोधित संस्करण का उपयोग करना है। यह पैटर्न का एक बहुत ही असामान्य उपयोग और भिन्नता है और मुझे लगता है कि लोगों का लाभ इस बात पर व्यापक रूप से भिन्न होगा कि क्या यह बिना किसी वास्तविक लाभ के लिए जटिलता को जोड़ रहा है या क्या यह स्थिति के लिए एक अच्छा समझौता है। विचार अनिवार्य रूप से मानक विज़िटर पैटर्न का उपयोग करने के लिए है, लेकिन उनके पास Visit
वे पैरामीटर हैं जिनकी वे जिस कक्षा में जा रहे हैं, उसके बजाय राज्य को पैरामीटर के रूप में उनकी आवश्यकता है। उदाहरण यहां देखे जा सकते हैं ।
हमारी समस्या के लिए, इस पैटर्न का उपयोग करने वाला एक समाधान होगा:
public class Coordinate
{
private readonly int _x;
private readonly int _y;
public T Transform<T>(IPositionTransformer<T> transformer)
{
return transformer.Transform(_x,_y);
}
}
public interface IPositionTransformer<T>
{
T Transform(int x, int y);
}
//This one lives in the presentation layer
public class CoordinateToVectorTransformer : IPositionTransformer<Vector2>
{
private readonly float _tileWidth;
private readonly float _tileHeight;
private readonly Vector2 _topLeft;
Vector2 Transform(int x, int y)
{
return _topLeft + new Vector2(_tileWidth*x + _tileHeight*y);
}
}
आप शायद बता सकते हैं, के रूप में _x
और _y
नहीं कर रहे हैं वास्तव में किसी भी अधिक समझाया। हम उन्हें बनाकर निकाल सकते हैं IPositionTransformer<Tuple<int,int>>
जो उन्हें सीधे लौटाता है। स्वाद के आधार पर, आप महसूस कर सकते हैं कि यह संपूर्ण व्यायाम को बेकार कर देता है।
हालांकि, सार्वजनिक गेटर्स के साथ, चीजों को गलत तरीके से करना बहुत आसान है, बस डेटा को सीधे बाहर निकालना और इसे टेल, डोंट आस्क के उल्लंघन में उपयोग करना है । इस पैटर्न का उपयोग करते हुए यह वास्तव में इसे सही तरीके से करने के लिए सरल है: जब आप व्यवहार बनाना चाहते हैं, तो आप स्वचालित रूप से इसके साथ एक प्रकार बनाकर शुरू करेंगे। TDA के उल्लंघन बहुत स्पष्ट रूप से बदबूदार होंगे और शायद एक सरल, बेहतर समाधान के आसपास काम करने की आवश्यकता होती है। व्यवहार में, ये बिंदु इसे सही करने के लिए बहुत आसान बनाते हैं, ओओ, जिस तरह से "बुराई" तरीके से यह प्रोत्साहित करता है कि गेटर्स प्रोत्साहित करते हैं।
अंत में , यहां तक कि अगर यह शुरू में स्पष्ट नहीं है, तो वास्तव में आपके द्वारा राज्य को उजागर करने की आवश्यकता से बचने के लिए व्यवहार के रूप में आपके द्वारा आवश्यक व्यवहार को उजागर करने के तरीके हो सकते हैं । उदाहरण के लिए, हमारे पिछले संस्करण का उपयोग करना Coordinate
जिसका एकमात्र सार्वजनिक सदस्य है Equals()
(व्यवहार में इसे पूर्ण IEquatable
कार्यान्वयन की आवश्यकता होगी ), आप अपनी प्रस्तुति परत में निम्न वर्ग लिख सकते हैं:
public class CoordinateToVectorTransformer
{
private Dictionary<Coordinate,Vector2> _coordinatePositions;
public CoordinateToVectorTransformer(int boardWidth, int boardHeight)
{
for(int x=0; x<boardWidth; x++)
{
for(int y=0; y<boardWidth; y++)
{
_coordinatePositions[new Coordinate(x,y)] = GetPosition(x,y);
}
}
}
private static Vector2 GetPosition(int x, int y)
{
//Some implementation goes here...
}
public Vector2 Transform(Coordinate coordinate)
{
return _coordinatePositions[coordinate];
}
}
यह पता चला है, शायद आश्चर्य की बात है, कि हमारे लक्ष्य को प्राप्त करने के लिए एक समन्वय से हमें वास्तव में जिस व्यवहार की आवश्यकता थी वह सभी समानता की जाँच थी! बेशक, यह समाधान इस समस्या के अनुरूप है, और स्वीकार्य स्मृति उपयोग / प्रदर्शन के बारे में धारणा बनाता है। यह केवल एक उदाहरण है जो सामान्य समाधान के लिए खाका के बजाय इस विशेष समस्या डोमेन को फिट करता है।
और फिर, राय अलग-अलग होगी कि क्या व्यवहार में यह अनावश्यक जटिलता है। कुछ मामलों में, इस तरह का कोई भी समाधान मौजूद नहीं हो सकता है, या यह निषेधात्मक रूप से अजीब या जटिल हो सकता है, जिस स्थिति में आप उपरोक्त तीनों पर वापस लौट सकते हैं।