Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trying to shy away from a singleton/god/manager class. Not sure how I am supposed to sustain functionality, though

I have a class which has been steadily growing over time. It's called LayoutManager.

It started as a way for me to keep track of which dynamically created controls were on my page. So, for instance, I have this:

public CormantRadDockZone()
{
    ID = String.Format("RadDockZone_{0}", Guid.NewGuid().ToString().Replace('-', 'a'));
    MinHeight = Unit.Percentage(100);
    BorderWidth = 0;
    HighlightedCssClass = "zoneDropOk";
    CssClass = "rightRoundedCorners";
    LayoutManager.Instance.RegisteredDockZones.Add(this);
}

In this way, during the beginning stages of the Page Lifecycle, controls would be re-created and they would add themselves to their respective control's list.

A while later I found myself passing the 'Page' object between methods. This was for the sole purpose of being able to access controls found on Page. I thought to myself -- well, I already have a Layout Manager, I'll just treat the static controls in the same way.

As such, my Page_Init method now looks like this mess:

protected void Page_Init(object sender, EventArgs e)
{
    SessionRepository.Instance.EnsureAuthorized();

    LayoutManager.Instance.RegisteredPanes.Clear();
    LayoutManager.Instance.RegisteredDocks.Clear();
    LayoutManager.Instance.RegisteredDockZones.Clear();
    LayoutManager.Instance.RegisteredSplitters.Clear();
    LayoutManager.Instance.RegisteredSplitBars.Clear();
    LayoutManager.Instance.RegisteredPageViews.Clear();

    LayoutManager.Instance.CheckBox1 = CheckBox1;
    LayoutManager.Instance.CheckBox4 = CheckBox4;

    LayoutManager.Instance.StartEditButton = StartEditButton;
    LayoutManager.Instance.FinishEditButton = FinishEditButton;

    LayoutManager.Instance.RadNumericTextBox1 = RadNumericTextBox1;
    LayoutManager.Instance.RadNumericTextBox2 = RadNumericTextBox2;

    LayoutManager.Instance.LeftPane = LeftPane;
    LayoutManager.Instance.DashboardUpdatePanel = DashboardUpdatePanel;

    LayoutManager.Instance.CustomReportsContainer = CustomReportsContainer;
    LayoutManager.Instance.HistoricalReportsContainer = HistoricalReportsContainer;
    RegenerationManager.Instance.RegenerateReportMenu();

    LayoutManager.Instance.MultiPage = DashboardMultiPage;
    LayoutManager.Instance.MultiPageUpdatePanel = MultiPageUpdatePanel;
    LayoutManager.Instance.TabStrip = DashboardTabStrip;

    RegenerationManager.Instance.RegenerateTabs(DashboardTabStrip);
    RegenerationManager.Instance.RegeneratePageViews();

    LayoutManager.Instance.Timer = RefreshAndCycleTimer;
    LayoutManager.Instance.Timer.TimerEvent += DashboardTabStrip.DoTimerCycleTick;

    RegenerationManager.Instance.RegeneratePageState();
}

I'm looking at that and saying no, no, no. That is all wrong. Yet, there are controls on my page which are very dependent on each other, but do not have access to each other. This is what seems to make this so necessary.

I think a good example of this in practice would be using UpdatePanels. So, for instance, DashboardUpdatePanel is being given to the LayoutManager. There are controls on the page which, conditionally, should cause the entire contents of the dashboard to update.

Now, in my eyes, I believe I have two options:

  1. Inside the object wanting to call UpdatePanel.Update(), I recurse up through parent objects, checking type and ID until I find the appropriate UpdatePanel.
  2. I ask LayoutManager for the UpdatePanel.

Clearly the second one sounds cleaner in this scenario... but I find myself using that same logic in many instances. This has resulted in a manager class which looks like this:

public class LayoutManager
{
    private static readonly ILog _logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

    private static readonly LayoutManager _instance = new LayoutManager();
    private LayoutManager() { }

    public static LayoutManager Instance
    {
        get { return _instance; }
    }

    private IList<CormantRadDock> _registeredDocks;
    private IList<CormantRadDockZone> _registeredDockZones;
    private IList<CormantRadPane> _registeredPanes;
    private IList<CormantRadSplitter> _registeredSplitters;
    private IList<CormantRadSplitBar> _registeredSplitBars; 
    private Dictionary<string, StyledUpdatePanel> _registeredUpdatePanels;
    private IList<CormantRadPageView> _registeredPageViews;

    public RadMultiPage MultiPage { get; set; }
    public CormantTimer Timer { get; set; }
    public CormantRadListBox HistoricalReportsContainer { get; set; }
    public CormantRadListBox CustomReportsContainer { get; set; }
    public StyledUpdatePanel MultiPageUpdatePanel { get; set; }
    public CormantRadTabStrip TabStrip { get; set; }
    public RadPane LeftPane { get; set; }
    public StyledUpdatePanel DashboardUpdatePanel { get; set; }
    public RadButton ToggleEditButton { get; set; }

    public CheckBox CheckBox1 { get; set; }
    public CheckBox CheckBox4 { get; set; }
    public RadNumericTextBox RadNumericTextBox1 { get; set; }
    public RadNumericTextBox RadNumericTextBox2 { get; set; }

    public RadButton StartEditButton { get; set; }
    public RadButton FinishEditButton { get; set; }

    public IList<CormantRadDock> RegisteredDocks
    {
        get
        {
            if (Equals(_registeredDocks, null))
            {
                _registeredDocks = new List<CormantRadDock>();
            }

            return _registeredDocks;
        }
    }

    public IList<CormantRadDockZone> RegisteredDockZones
    {
        get
        {
            if (Equals(_registeredDockZones, null))
            {
                _registeredDockZones = new List<CormantRadDockZone>();
            }

            return _registeredDockZones;
        }
    }

    public IList<CormantRadPane> RegisteredPanes
    {
        get
        {
            if (Equals(_registeredPanes, null))
            {
                _registeredPanes = new List<CormantRadPane>();
            }

            return _registeredPanes;
        }
    }

    public IList<CormantRadSplitter> RegisteredSplitters
    {
        get
        {
            if (Equals(_registeredSplitters, null))
            {
                _registeredSplitters = new List<CormantRadSplitter>();
            }

            return _registeredSplitters;
        }
    }

    public IList<CormantRadSplitBar> RegisteredSplitBars
    {
        get
        {
            if (Equals(_registeredSplitBars, null))
            {
                _registeredSplitBars = new List<CormantRadSplitBar>();
            }

            return _registeredSplitBars;
        }
    }

    public Dictionary<string, StyledUpdatePanel> RegisteredUpdatePanels
    {
        get
        {
            if( Equals( _registeredUpdatePanels, null))
            {
                _registeredUpdatePanels = new Dictionary<string, StyledUpdatePanel>();
            }

            return _registeredUpdatePanels;
        }
    }

    public IList<CormantRadPageView> RegisteredPageViews
    {
        get
        {
            if (Equals(_registeredPageViews, null))
            {
                _registeredPageViews = new List<CormantRadPageView>();
            }

            return _registeredPageViews;
        }
    }

    public StyledUpdatePanel GetBaseUpdatePanel()
    {
        string key = MultiPage.PageViews.Cast<CormantRadPageView>().Where(pageView => pageView.Selected).First().ID;
        return RegisteredUpdatePanels[key];
    }

    public CormantRadDockZone GetDockZoneByID(string dockZoneID)
    {
        CormantRadDockZone dockZone = RegisteredDockZones.Where(registeredZone => dockZoneID.Contains(registeredZone.ID)).FirstOrDefault();

        if (Equals(dockZone, null))
        {
            _logger.ErrorFormat("Did not find dockZone: {0}", dockZoneID);
        }
        else
        {
            _logger.DebugFormat("Found dockZone: {0}", dockZoneID);
        }

        return dockZone;
    }

    public CormantRadPane GetPaneByID(string paneID)
    {
        CormantRadPane pane = RegisteredPanes.Where(registeredZone => paneID.Contains(registeredZone.ID)).FirstOrDefault();

        if (Equals(pane, null))
        {
            _logger.ErrorFormat("Did not find pane: {0}", paneID);
        }
        else
        {
            _logger.DebugFormat("Found pane: {0}", paneID);
        }

        return pane;
    }

    public CormantRadDock GetDockByID(string dockID)
    {
        CormantRadDock dock = RegisteredDocks.Where(registeredZone => dockID.Contains(registeredZone.ID)).FirstOrDefault();

        if (Equals(dock, null))
        {
            _logger.ErrorFormat("Did not find dock: {0}", dockID);
        }
        else
        {
            _logger.DebugFormat("Found dock: {0}", dockID);
        }

        return dock;
    }
}

Am I on a bad path? What steps are generally taken at this point?

EDIT1: I have decided to start down the path of improvement by finding the controls which are least-integrated into LayoutManager and finding ways of breaking them down into separate objects. So, for instance, instead of assigning the HistoricalReportsContainer and CustomReportsContainer objects to LayoutManager (which is then used in RegenerationManager.RegenerateReportMenu) I have moved the code to RadListBox "Load" event. There, I check the ID of the control which is loading and react accordingly. A strong first improvement, and has removed 2 controls and a method from LayoutManager!

like image 403
Sean Anderson Avatar asked Oct 28 '11 21:10

Sean Anderson


People also ask

Why should you avoid singletons?

The most important drawback of the singleton pattern is sacrificing transparency for convenience. Consider the earlier example. Over time, you lose track of the objects that access the user object and, more importantly, the objects that modify its properties.

How can we avoid Singleton?

Suppose you serialize an object of a singleton class. Then if you de-serialize that object it will create a new instance and hence break the singleton pattern.

How do I override Singleton class?

The only way to override a singleton is to have a singleton that expects to be overridden. The simplest way to do this is to provide Singleton that implements an interface (or is otherwise fully abstract itself) that internally instantiates an injected singleton upon the first use of getInstance() .

When and why singletons is bad in large scalable applications?

Singletons are also bad when it comes to clustering. Because then, you do not have "exactly one singleton" in your application anymore. Consider the following situation: As a developer, you have to create a web application which accesses a database.


1 Answers

Inversion of control is a general approach that people use for such problems. Your dependencies should not be stored in the one Jack-Bauer-kind-of-style class, but rather be injected, for example via constructor. Take a look at the IoC containers, such as Castle Windsor, Unity, NInject or any other.

like image 96
oleksii Avatar answered Sep 18 '22 14:09

oleksii