कैशिंग का प्रबंधन करने के लिए कक्षा में SRP के उल्लंघन से कैसे बचें?


12

नोट: कोड नमूना c # में लिखा गया है, लेकिन इससे कोई फर्क नहीं पड़ता। मैंने एक टैग के रूप में c # डाला है क्योंकि मैं एक और अधिक एप्रीप्रिएट नहीं कर सकता। यह कोड संरचना के बारे में है।

मैं क्लीन कोड पढ़ रहा हूं और एक बेहतर प्रोग्रामर बनने की कोशिश कर रहा हूं।

मैं अक्सर खुद को सिंगल रिस्पांसिबिलिटी प्रिंसिपल (क्लासेस और फ़ंक्शंस केवल एक ही काम करना चाहिए) का पालन करने के लिए संघर्ष करता हूं, विशेष रूप से फ़ंक्शंस में। शायद मेरी समस्या यह है कि "एक बात" अच्छी तरह से परिभाषित नहीं है, लेकिन फिर भी ...

एक उदाहरण: मेरे पास एक डेटाबेस में Fluffies की एक सूची है। हमें परवाह नहीं है कि एक शराबी क्या है। मैं फुलझड़ी ठीक करने के लिए एक वर्ग चाहता हूं। हालांकि, फुलझड़ी कुछ तर्क के अनुसार बदल सकती है। कुछ तर्क के आधार पर, यह वर्ग कैश से डेटा लौटाएगा या डेटाबेस से नवीनतम प्राप्त करेगा। हम कह सकते हैं कि यह फुलझड़ी का प्रबंधन करता है, और यह एक बात है। इसे सरल बनाने के लिए, मान लें कि लोड किया गया डेटा एक घंटे के लिए अच्छा है, और फिर इसे पुनः लोड किया जाना चाहिए।

class FluffiesManager
{
    private Fluffies m_Cache;
    private DateTime m_NextReload = DateTime.MinValue;
    // ...
    public Fluffies GetFluffies()
    {
        if (NeedsReload())
            LoadFluffies();

        return m_Cache;
    }

    private NeedsReload()
    {
        return (m_NextReload < DateTime.Now);
    }

    private void LoadFluffies()
    {
        GetFluffiesFromDb();
        UpdateNextLoad();
    }

    private void UpdateNextLoad()
    {
        m_NextReload = DatTime.Now + TimeSpan.FromHours(1);
    }
    // ...
}

GetFluffies()मुझे ठीक लगता है। उपयोगकर्ता कुछ फुलझड़ियों के लिए पूछता है, हम उन्हें प्रदान करते हैं। जरूरत पड़ने पर उन्हें डीबी से उबारने के लिए जा रहे हैं, लेकिन इसे फ्लफी पाने का एक हिस्सा माना जा सकता है (निश्चित रूप से, यह कुछ विषय है)।

NeedsReload()सही भी लगता है। जाँच करता है कि क्या हमें फ़्लॉफ़ी को फिर से लोड करना है। UpdateNextLoad ठीक है। अगले पुनः लोड के लिए समय अद्यतन करता है। यह निश्चित रूप से एक ही बात है।

हालांकि, मुझे लगता है कि LoadFluffies()एक ही बात के रूप में वर्णित नहीं किया जा सकता है। यह डेटाबेस से डेटा प्राप्त कर रहा है, और यह अगले पुनः लोड को शेड्यूल कर रहा है। यह तर्क देना मुश्किल है कि अगले पुनः लोड के लिए समय की गणना डेटा प्राप्त करने का हिस्सा है। हालाँकि, मैं इसे करने के लिए एक बेहतर तरीका नहीं खोज सकता (फ़ंक्शन का नाम बदलना LoadFluffiesAndScheduleNextLoadबेहतर हो सकता है, लेकिन यह समस्या को और अधिक स्पष्ट करता है)।

क्या SRP के अनुसार वास्तव में इस वर्ग को लिखने का एक सुंदर समाधान है? क्या मैं बहुत पांडित्यपूर्ण हूं?

या शायद मेरी कक्षा वास्तव में सिर्फ एक काम नहीं कर रही है?


3
"C # में लिखा है, लेकिन यह बात नहीं होनी चाहिए" के आधार पर, "यह कोड संरचना के बारे में है", "एक उदाहरण: ... हमें परवाह नहीं है कि एक शराबी क्या है", "इसे सरल बनाने के लिए, मान लीजिए ...", यह एक कोड समीक्षा के लिए अनुरोध नहीं है, लेकिन एक सामान्य प्रोग्रामिंग सिद्धांत के बारे में एक सवाल है।
२००

@ 200_success धन्यवाद, और खेद है, मैंने सोचा था कि यह सीआर के लिए पर्याप्त होगा
रैवेन


2
भविष्य में आप भविष्य के समान सवालों के लिए शराबी के बजाय "विजेट" के साथ बेहतर होंगे, क्योंकि एक विजेट को उदाहरणों के लिए एक गैर-विशेष स्टैंड माना जाता है।
whatsisname

1
मुझे पता है कि यह केवल उदाहरण कोड है, लेकिन इसका उपयोग करें DateTime.UtcNowताकि आप दिन के उजाले में बचत से बचें, या वर्तमान समय में बदलाव भी कर सकें।
मार्क हर्ड

जवाबों:


23

यदि यह वर्ग वास्तव में उतना ही तुच्छ था जितना प्रतीत होता है, तो एसआरपी के उल्लंघन के बारे में चिंता करने की आवश्यकता नहीं होगी। तो क्या होगा अगर एक 3-लाइन फ़ंक्शन में एक काम करने वाली 2 लाइनें हैं, और दूसरी 1 लाइन दूसरी चीज़ कर रही है? हां, यह तुच्छ कार्य SRP का उल्लंघन करता है, और इसलिए क्या? किसे पड़ी है? जब चीजें अधिक जटिल हो जाती हैं तो एसआरपी का उल्लंघन एक समस्या बनने लगती है।

इस विशेष मामले में आपकी समस्या शायद इस तथ्य से उपजी है कि आपने जो कुछ पंक्तियाँ हमें दिखाई हैं, उनकी तुलना में वर्ग अधिक जटिल है।

विशेष रूप से, समस्या शायद सबसे अधिक इस तथ्य में निहित है कि यह वर्ग न केवल कैश का प्रबंधन करता है, बल्कि संभवतः GetFluffiesFromDb()विधि का कार्यान्वयन भी शामिल है । इसलिए, SRP का उल्लंघन वर्ग में है, उन कुछ तुच्छ तरीकों में नहीं, जो आपके द्वारा पोस्ट किए गए कोड में दिखाए गए हैं।

तो, यहाँ एक सुझाव है कि इस सामान्य श्रेणी के भीतर आने वाले सभी प्रकार के मामलों को कैसे संभालें, डेकोरेटर पैटर्न की मदद से ।

/// Provides Fluffies.
interface FluffiesProvider
{
    Fluffies GetFluffies();
}

/// Implements FluffiesProvider using a database.
class DatabaseFluffiesProvider : FluffiesProvider
{
    public override Fluffies GetFluffies()
    {
        ... load fluffies from DB ...
        (the entire implementation of "GetFluffiesFromDb()" goes here.)
    }
}

/// Decorates FluffiesProvider to add caching.
class CachingFluffiesProvider : FluffiesProvider
{
    private FluffiesProvider decoree;
    private DateTime m_NextReload = DateTime.MinValue;
    private Fluffies m_Cache;

    public CachingFluffiesProvider( FluffiesProvider decoree )
    {
        Assert( decoree != null );
        this.decoree = decoree;
    }

    public override Fluffies GetFluffies()
    {
        if( DateTime.Now >= m_NextReload ) 
        {
             m_Cache = decoree.GetFluffies();
             m_NextReload = DatTime.Now + TimeSpan.FromHours(1);
        }
        return m_Cache;
    }
}

और इसका उपयोग इस प्रकार किया जाता है:

FluffiesProvider provider = new DatabaseFluffiesProvider();
provider = new CachingFluffiesProvider( provider );
...go ahead and use provider...

ध्यान दें कि CachingFluffiesProvider.GetFluffies()कोड को चेक करने और अपडेट करने में डर नहीं है, क्योंकि यह सामान है। यह तंत्र क्या करता है, सिस्टम डिज़ाइन स्तर पर SRP को संबोधित और संभालना है, जहां यह मायने रखता है, न कि छोटे व्यक्तिगत तरीकों के स्तर पर, जहाँ यह किसी भी तरह से मायने नहीं रखता है।


1
+1 यह पहचानने के लिए कि फुलझड़ी, कैशिंग और डेटा बेस एक्सेस वास्तव में तीन जिम्मेदारियां हैं। तुम भी FluffiesProvider इंटरफ़ेस और सज्जाकार जेनेरिक (IProvider <शराबी>, ...) बनाने की कोशिश कर सकते हैं, लेकिन यह YAGNI हो सकता है।
रोमन रेनर

ईमानदारी से, यदि केवल एक ही प्रकार का कैश है और यह हमेशा डेटाबेस से ऑब्जेक्ट्स को खींचता है, तो यह IMHO भारी ओवरडाइन किया गया है (भले ही "वास्तविक" वर्ग अधिक जटिल हो सकता है जैसा कि हम उदाहरण में देख सकते हैं)। एब्स्ट्रैक्शन के लिए एब्सट्रैक्शन सिर्फ कोड क्लीनर या अधिक रखरखाव योग्य नहीं है।
डॉक ब्राउन

@DocBrown समस्या प्रश्न के संदर्भ की कमी है। मुझे यह उत्तर पसंद है बीक्युस यह एक ऐसा तरीका दिखाता है जिसे मैंने बड़े अनुप्रयोगों में समय और समय का फिर से उपयोग किया है और क्योंकि इसके खिलाफ परीक्षण लिखना आसान है, मुझे मेरा उत्तर भी पसंद है क्योंकि यह केवल एक छोटा सा बदलाव है और बिना किसी अतिव्यापन के कुछ स्पष्ट करता है। वर्तमान में, संदर्भ के बिना, बहुत सारे उत्तर अच्छे हैं:]
12

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

2
@stijn: ठीक है, मुझे लगता है कि आपका जवाब बहुत कम है। अनावश्यक अमूर्तता जोड़ने के बजाय, आप सिर्फ जिम्मेदारियों को काटते हैं और नाम देते हैं, जो कि इस तरह की साधारण समस्या के लिए विरासत की तीन परतों को जमा करने से पहले हमेशा पहली पसंद होनी चाहिए।
डॉक्टर ब्राउन

6

आपकी कक्षा मुझे ही ठीक लगती है, लेकिन आप सही कह रहे हैं कि LoadFluffies()वास्तव में नाम क्या विज्ञापित करता है। एक सरल समाधान यह होगा कि नाम बदलें और एक उचित विवरण के साथ एक फ़ंक्शन में GetFluffies से स्पष्ट पुनः लोडिंग को स्थानांतरित करें। कुछ इस तरह

public Fluffies GetFluffies()
{
  MakeSureTheFluffyCacheIsUpToDate();
  return m_Cache;
}

private void MakeSureTheFluffyCacheIsUpToDate()
{
  if( !NeedsReload )
    return;
  GetFluffiesFromDb();
  SetNextReloadTime();
}

मुझे साफ दिखता है (इसलिए भी कि जैसे पैट्रिक कहता है: यह अन्य छोटे एसआरपी-आज्ञाकारी कार्यों से बना है), और विशेष रूप से यह भी स्पष्ट है कि कभी-कभी बस उतना ही महत्वपूर्ण होता है।


1
मुझे इसमें सादगी पसंद है।
रैवेन

6

मेरा मानना ​​है कि आपका वर्ग एक काम कर रहा है; यह टाइमआउट के साथ डेटा कैश है। LoadFluffies एक बेकार अमूर्त की तरह लगता है जब तक आप इसे कई स्थानों से नहीं बुलाते हैं। मुझे लगता है कि LoadFluffies से दो लाइनें लेना और GetFluffies में NeedsReload सशर्त में डालना बेहतर होगा। यह GetFluffies के कार्यान्वयन को बहुत अधिक स्पष्ट बना देगा और अभी भी साफ कोड है, क्योंकि आप एक ही लक्ष्य को पूरा करने के लिए एकल जिम्मेदारी उप-प्रक्रिया की रचना कर रहे हैं, db से डेटा की एक कैश्ड पुनर्प्राप्ति। नीचे अद्यतन प्राप्त fluffies विधि है।

public Fluffies GetFluffies()
{
    if (NeedsReload()) {
        GetFluffiesFromDb();
        UpdateNextLoad();
    }

    return m_Cache;
}

हालांकि यह एक बहुत अच्छा पहला उत्तर है, कृपया ध्यान रखें कि "परिणाम" कोड अक्सर एक अच्छा जोड़ होता है।
निधि मोनिका का मुकदमा

4

आपकी वृत्ति सही है। आपकी कक्षा, हालांकि यह बहुत छोटी है, बहुत अधिक कर रही है। आपको समयबद्ध ताज़ा कैशिंग लॉजिक को पूरी तरह से सामान्य श्रेणी में अलग करना चाहिए। फिर Fluffies के प्रबंधन के लिए उस वर्ग का एक विशिष्ट उदाहरण बनाएं, कुछ इस तरह (संकलित नहीं, काम करने वाले कोड को पाठक के लिए एक अभ्यास के रूप में छोड़ दिया जाता है):

public class TimedRefreshCache<T> {
    T m_Value;
    DateTime m_NextLoadTime;
    Func<T> m_producer();
    public CacheManager(Func<T> T producer, Interval timeBetweenLoads) {
          m_nextLoadTime = INFINITE_PAST;
          m_producer = producer;
    }
    public T Value {
        get {
            if (m_NextLoadTime < DateTime.Now) {
                m_Value = m_Producer();
                m_NextLoadTime = ...;
            }
            return m_Value;
        }
    }
}

public class FluffyCache {
    private TimedRefreshCache m_Cache 
        = new TimedRefreshCache<Fluffy>(GetFluffiesFromDb, interval);
    private Fluffy GetFluffiesFromDb() { ... }
    public Fluffy Value { get { return m_Cache.Value; } }
}

एक अतिरिक्त लाभ यह है कि अब TimedRefreshCache का परीक्षण करना बहुत आसान है।


1
मैं मानता हूं कि यदि ताज़ा तर्क उदाहरण की तुलना में अधिक जटिल हो जाता है, तो इसे अलग वर्ग में फिर से सम्मिलित करना एक अच्छा विचार हो सकता है। लेकिन मैं असहमत हूं कि उदाहरण में वर्ग, जैसा भी है, बहुत अधिक करता है।
डॉक ब्राउन

@kevin, मैं TDD में अनुभवी नहीं हूं। क्या आप विस्तार से बताएंगे कि आप TimedRefreshCache का परीक्षण कैसे करेंगे? मैं इसे "बहुत आसान" के रूप में नहीं देखता, लेकिन यह मेरी विशेषज्ञता की कमी हो सकती है।
रावेन

1
मैं व्यक्तिगत रूप से आपके जवाब को पसंद नहीं करता क्योंकि यह जटिलता है। यह बहुत सामान्य और बहुत सार है और अधिक जटिल परिस्थितियों में सबसे अच्छा हो सकता है। लेकिन इस साधारण मामले में यह 'सरलता से बहुत कुछ' है। कृपया stijn के जवाब पर एक नज़र डालें। कितना अच्छा, छोटा और पठनीय जवाब। हर कोई इसे imediatly समझ जाएगा। तुम क्या सोचते हो?
डाइटर मीकेन

1
@raven आप थोड़े अंतराल (जैसे 100ms) और बहुत ही साधारण निर्माता (जैसे DateTime.Now) का उपयोग करके TimedRefreshCache का परीक्षण कर सकते हैं। प्रत्येक 100 एमएस कैश एक नए मूल्य का उत्पादन करेगा, बीच में यह पिछले मूल्य को लौटाएगा।
केविन क्लाइन

1
@DocBrown: समस्या यह है कि जैसा लिखा गया है वह अप्राप्य है। टाइमिंग लॉजिक (परीक्षण योग्य) को डेटाबेस लॉजिक के साथ जोड़ा जाता है, जो तब बहुत मज़ाक उड़ाया जाता है। एक बार डेटाबेस कॉल को मॉक करने के लिए सीम बनाया जाता है, तो आप जेनेरिक समाधान के लिए 95% हैं। मैंने पाया है कि इन छोटी कक्षाओं का निर्माण आमतौर पर बंद हो जाता है क्योंकि वे अंत में उम्मीद से अधिक पुन: उपयोग किए जाते हैं।
केविन क्लाइन

1

आपकी कक्षा ठीक है, एसआरपी एक ऐसा वर्ग है जो एक फ़ंक्शन नहीं है, पूरी कक्षा "डेटा स्रोत" से "फ़्लफ़ीज़" प्रदान करने के लिए जिम्मेदार है, इसलिए आप आंतरिक कार्यान्वयन में स्वतंत्र हैं।

यदि आप काहिंग तंत्र का विस्तार करना चाहते हैं, तो आप डेटा स्रोत को देखने के लिए कक्षा को प्रतिक्रियाशील बना सकते हैं

public class ModelWatcher
{

    private static Dictionary<Type, DateTime> LastUpdate;

    public static bool IsUpToDate(Type entityType, DateTime lastRead) {
        if (LastUpdate.ContainsKey(entityType)) {
            return lastRead >= LastUpdate[entityType];
        }
        return true;
    }

    //call this method whenever insert/update changed to any entity
    private void OnDataSourceChanged(Type changedEntityType) {
        //update Date & Time
        LastUpdate[changedEntityType] = DateTime.Now;
    }
}
public class FluffyManager
{
    private DateTime LastRead = DateTime.MinValue;

    private List<Fluffy> list;



    public List<Fluffy> GetFluffies() {

        //if first read or not uptodated
        if (list==null || !ModelWatcher.IsUpToDate(typeof(Fluffy),LastRead)) {
            list = ReadFluffies();
        }
        return list;
    }
    private List<Fluffy> ReadFluffies() { 
    //read code
    }
}

अंकल बॉब के अनुसार: FUNCTIONS SHOULD DO ONE THING। वे इसे करना चाहते हैं। वे केवल यह करना चाहिए। साफ कोड p.35।
रैवेन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.