वर्तमान स्थिति
वर्तमान सेटअप इंटरफ़ेस अलगाव सिद्धांत (SOLID में I) का उल्लंघन करता है।
संदर्भ
विकिपीडिया के अनुसार इंटरफ़ेस अलगाव सिद्धांत (आईएसपी) कहता है कि किसी भी ग्राहक को उन तरीकों पर निर्भर होने के लिए मजबूर नहीं किया जाना चाहिए जो इसका उपयोग नहीं करते हैं । इंटरफ़ेस अलगाव का सिद्धांत रॉबर्ट मार्टिन द्वारा 1990 के दशक के मध्य में तैयार किया गया था।
दूसरे शब्दों में, अगर यह आपका इंटरफ़ेस है:
public interface IUserBackend
{
User getUser(int uid);
User createUser(int uid);
void deleteUser(int uid);
void setPassword(int uid, string password);
}
फिर इस इंटरफ़ेस को लागू करने वाले प्रत्येक वर्ग को इंटरफ़ेस के प्रत्येक सूचीबद्ध तरीके का उपयोग करना चाहिए । कोई अपवाद नहीं।
कल्पना करें कि क्या कोई सामान्यीकृत विधि है:
public void HaveUserDeleted(IUserBackend backendService, User user)
{
backendService.deleteUser(user.Uid);
}
यदि आप वास्तव में इसे बना रहे थे ताकि केवल कुछ कार्यान्वयन कक्षाएं वास्तव में एक उपयोगकर्ता को हटाने में सक्षम हों, तो यह विधि कभी-कभी आपके चेहरे पर उड़ जाएगी (या कुछ भी नहीं करें)। यह अच्छा डिज़ाइन नहीं है।
आपका प्रस्तावित समाधान
मैंने एक समाधान देखा है जहां IUserInterface में एक कार्यान्वित विधि है जो एक पूर्णांक लौटाती है जो कि बिटवाइज़ के कार्यों का परिणाम है।
आप अनिवार्य रूप से क्या करना चाहते हैं:
public void HaveUserDeleted(IUserBackend backendService, User user)
{
if(backendService.canDeleteUser())
backendService.deleteUser(user.Uid);
}
मैं इस बात की अनदेखी कर रहा हूं कि वास्तव में हम यह निर्धारित करते हैं कि एक दिया गया वर्ग किसी उपयोगकर्ता को हटाने में सक्षम है या नहीं। चाहे वह बूलियन हो, थोड़ा झंडा हो, ... कोई फर्क नहीं पड़ता। यह सभी एक द्विआधारी उत्तर को उबालता है: क्या यह उपयोगकर्ता को हटा सकता है, हां या नहीं?
यह समस्या को हल करेगा, है ना? ठीक है, तकनीकी रूप से, यह करता है। लेकिन अब, आप Liskov प्रतिस्थापन सिद्धांत (SOLID में L) का उल्लंघन कर रहे हैं ।
बल्कि जटिल विकिपीडिया स्पष्टीकरण के कारण, मुझे StackOverflow पर एक अच्छा उदाहरण मिला । "खराब" उदाहरण पर ध्यान दें:
void MakeDuckSwim(IDuck duck)
{
if (duck is ElectricDuck)
((ElectricDuck)duck).TurnOn();
duck.Swim();
}
मुझे लगता है कि आप यहाँ समानता देखते हैं। यह एक ऐसी विधि है जिसे एक अमूर्त वस्तु ( IDuck, IUserBackend) को संभालने के लिए माना जाता है , लेकिन एक समझौता किए गए वर्ग के डिजाइन के कारण, इसे पहले विशिष्ट कार्यान्वयन को संभालने के लिए मजबूर किया जाता है ( ElectricDuckयह सुनिश्चित करें कि यह एक IUserBackendवर्ग नहीं है जो उपयोगकर्ताओं को हटा नहीं सकता है)।
यह एक सार दृष्टिकोण विकसित करने के उद्देश्य को हराता है।
नोट: यहाँ उदाहरण आपके मामले से ठीक करने के लिए आसान है। उदाहरण के लिए, यह करने के लिए पर्याप्त होता है ElectricDuckपर बारी ही अंदरSwim() विधि। दोनों बतख अभी भी तैरने में सक्षम हैं, इसलिए कार्यात्मक परिणाम समान है।
आप ऐसा ही कुछ करना चाह सकते हैं। नहीं है । आप केवल एक उपयोगकर्ता को हटाने का दिखावा नहीं कर सकते, लेकिन वास्तव में एक खाली तरीका है। हालांकि यह एक तकनीकी दृष्टिकोण से काम करता है, लेकिन यह जानना असंभव है कि क्या आपके कार्यान्वयन वर्ग वास्तव में कुछ करने के लिए कहने पर कुछ करेंगे या नहीं। यह अकल्पनीय कोड के लिए एक प्रजनन मैदान है।
मेरा प्रस्तावित समाधान
लेकिन आपने कहा कि कार्यान्वयन वर्ग के लिए यह संभव है (और सही) केवल इनमें से कुछ तरीकों को संभालना।
उदाहरण के लिए, मान लें कि इन विधियों के हर संभव संयोजन के लिए, एक वर्ग है जो इसे लागू करेगा। यह हमारे सभी ठिकानों को कवर करता है।
इसका समाधान इंटरफ़ेस को विभाजित करना है ।
public interface IGetUserService
{
User getUser(int uid);
}
public interface ICreateUserService
{
User createUser(int uid);
}
public interface IDeleteUserService
{
void deleteUser(int uid);
}
public interface ISetPasswordService
{
void setPassword(int uid, string password);
}
ध्यान दें कि आपने मेरे उत्तर की शुरुआत में इसे देखा है। इंटरफ़ेस पृथक्करण सिद्धांत नाम पहले से ही पता चलता है कि इस सिद्धांत आप बनाने के लिए डिज़ाइन किया गया है इंटरफेस अलग एक पर्याप्त डिग्री करने के लिए।
यह आपको मिक्स-एंड-मैच इंटरफेस की अनुमति देता है, जैसा कि आप कृपया:
public class UserRetrievalService
: IGetUserService, ICreateUserService
{
//getUser and createUser methods implemented here
}
public class UserDeleteService
: IDeleteUserService
{
//deleteUser method implemented here
}
public class DoesEverythingService
: IGetUserService, ICreateUserService, IDeleteUserService, ISetPasswordService
{
//All methods implemented here
}
प्रत्येक वर्ग यह तय कर सकता है कि वे क्या करना चाहते हैं, बिना अपने इंटरफेस के अनुबंध को तोड़ने के।
इसका अर्थ यह भी है कि हमें यह जाँचने की आवश्यकता नहीं है कि एक निश्चित वर्ग किसी उपयोगकर्ता को हटाने में सक्षम है या नहीं। IDeleteUserServiceइंटरफ़ेस को लागू करने वाला प्रत्येक वर्ग एक उपयोगकर्ता को हटाने में सक्षम होगा = लिस्कोव प्रतिस्थापन सिद्धांत का कोई उल्लंघन नहीं ।
public void HaveUserDeleted(IDeleteUserService backendService, User user)
{
backendService.deleteUser(user.Uid); //guaranteed to work
}
यदि कोई ऐसी वस्तु को पारित करने की कोशिश करता है जो लागू नहीं होती है IDeleteUserService, तो कार्यक्रम संकलन के लिए मना कर देगा। यही कारण है कि हम टाइप सेफ्टी रखना पसंद करते हैं।
HaveUserDeleted(new DoesEverythingService()); // No problem.
HaveUserDeleted(new UserDeleteService()); // No problem.
HaveUserDeleted(new UserRetrievalService()); // COMPILE ERROR
पाद लेख
मैंने उदाहरण को एक चरम पर ले लिया, इंटरफ़ेस को सबसे छोटी संभव विखंडू में अलग कर दिया। हालांकि, यदि आपकी स्थिति अलग है, तो आप बड़ी मात्रा में भाग ले सकते हैं।
उदाहरण के लिए, यदि प्रत्येक सेवा जो एक उपयोगकर्ता बना सकती है वह हमेशा उपयोगकर्ता (और इसके विपरीत) को हटाने में सक्षम होती है, तो आप इन विधियों को एकल इंटरफ़ेस के भाग के रूप में रख सकते हैं:
public interface IManageUserService
{
User createUser(int uid);
void deleteUser(int uid);
}
छोटे विखंडों को अलग करने के बजाय ऐसा करने में कोई तकनीकी लाभ नहीं है; लेकिन यह विकास को थोड़ा आसान बना देगा क्योंकि इसमें कम बॉयलरप्लेटिंग की आवश्यकता होती है।
IUserBackendशामिल नहीं होना चाहिएdeleteUserसब पर विधि। इसका हिस्सा होना चाहिएIUserDeleteBackend(या जिसे आप इसे कॉल करना चाहते हैं)। कोड जिसे उपयोगकर्ताओं को हटाने की आवश्यकता होगी, के पास तर्क होंगेIUserDeleteBackend, कोड की आवश्यकता नहीं है जो कार्यक्षमता का उपयोग करेगाIUserBackendऔर अभेद्य तरीकों से कोई परेशानी नहीं होगी ।