Click here to Skip to main content
15,879,096 members
Articles / Programming Languages / C#

Patterns for controlling an object's public operations outside of it

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
22 May 2017CPOL5 min read 9.7K   6   3
We frequently need to control (or limit) the public operations of an object based on the sate of another object (mostly the owner). I have listed four ways of dealing with such situations with a sample player, playlist scenario.

Introduction

We frequently need to control (or limit) the public operations of an object based on the current state of another object (mostly the owner).

For example, you have a playlist class that has some properties, methods and a collection of playlist items. You want to prevent manipulation of the playlist item collection if player is playing this playlist.

Sample codes are partial implementaitons of sample playlist and related classes. Preventing manipulations on the playlist item itself is omitted for simplicity.

Common solutions

There are many solutions to these type of situations, four most common of them are as follows;

1- Expose the playlist items as read-only collection and provide manipulation methods in the playlist class

2- Use internal methods or properties

3- Use cancellable pre-manipulation events

4- Hook into the public operations with a hooking interface

1 - Expose the playlist items as read-only collection and provide manipulation methods in the playlist class

You may expose the playlist items as a read-only collection and provide manipulation methods (add, remove...) in the playlist itself.

Sample implementation for read-only item collection and public manipulation methods is as follows;

C#
public class PlaylistWithReadOnlyItems
{
    List<PlaylistItem> m_Items;

    public IList<PlaylistItem> Items { get; private set; }

    public PlaylistWithReadOnlyItems()
    {
        m_Items = new List<PlaylistItem>();

        //Expose read-only version of the list in order to prevent direct add-remove operations
        this.Items = m_Items.AsReadOnly();
    }

    public void AddItem(PlaylistItem item)
    {
        m_Items.Add(item);
    }

    public void RemoveItem(PlaylistItem item)
    {
        m_Items.Remove(item);
    }

    //.....
}

The disadvantage of this pattern is that, using the Items property for reading and public methods for manipulating the collection may be confusing for developers. Because developers are accustomed to use exposed collection porperties for both reading and manipulating its items.

Another way of exposing inner items for read-only purposes is providing an indexer which has only get operator.

C#
public PlaylistItem this[int index]
{
    get
    {
        return m_Items[index];
    }            
}

2 - Use internal methods or properties

If you are developing a SDK or a component library that will be used by others, managing and protecting the internal state can be accomplished by using internal methods or properties.

The first step is to add internal control methods or properties to the class that will be under control. This sample uses internal properties in order to allow or prevent item operations on the custom collection class.

C#
public class PlaylistWithInternalControlProperties : IList<PlaylistItem>
{
    List<PlaylistItem> m_Items;

    internal bool AllowAdd { get; set; }//Internal property for controlling "Add" operations 
    internal bool AllowRemove { get; set; }//Internal property for controlling "Remove" operations

    //....

    public void Add(PlaylistItem item)
    {
        if (this.AllowAdd)//Add the item if it is allowed
            m_Items.Add(item);
        else
        {
            //Maybe throw exception
        }
    }

    public bool Remove(PlaylistItem item)
    {
        if (this.AllowRemove)//Remove the item if it is allowed
            return m_Items.Remove(item);

        return false;
    }
    //....
}

The next step is to control item operations from the owner object by settings the internal properties of the controlled object based on the current state. For example, in the below sample, adding and removing items are not allowed if the player is playing the list currently.

C#
public class Player
{
    PlaylistWithInternalControlProperties m_playlist;
    PlayerState m_State;

    public PlayerState State
    {
        get { return m_State; }
        set
        {
            m_State = value;
               
            //Allow or deny item operations on the playlist based on the current state of this player
            m_playlist.AllowAdd = m_State != PlayerState.Playing;
            m_playlist.AllowRemove = m_State != PlayerState.Playing;
        }
    }

    //..          
}

3 - Use cancellable pre-manipulation events

This pattern is very common in .Net Framework and 3rd party class libraries.  This pattern makes use of events for notfying the owner about the changes that is going to be done on an object and give a chnace to cancel the operation.

The first step is to define operation types (mostly an enum definition) and an event args class that contains the information about the operation that is going to be done and has a Cancel property. System.ComponentModel.CancelEventArgs class can be used as the base class for the new event args class.

Sample operations enum and event args class are as follows;

C#
public enum ItemOperation
{
    Add,
    Insert,
    Remove,
    Clear
}

//Event args class for holding the information about the item operation that is going to be done
public class PlaylistItemEventArgs : CancelEventArgs
{
    //Related playlist item
    public PlaylistItem Item { get; private set; }
    
    //The operation that is goin to be done
    public ItemOperation Operation { get; private set; }

    public PlaylistItemEventArgs(PlaylistItem item, ItemOperation operation)
    {
        this.Item = item;
        this.Operation = operation;
    }
}

//Delegate for pre-manipulation event declarations
public delegate void PlaylistItemEventHandler(object sender, PlaylistItemEventArgs e);

Next step is to add pre-manipulation events (like ItemAdding, ItemRemoving, SomePropertyChanging) and fire them before the operation is performed.

Third step is to cancel the operation if the Cancel property of the event args class is set to true after event processing is finished. There are two options for firing the pre-manipulation event and checking its Cancel property.

The first option is to call all registered event handlers and check the Cancel property at the end. OnItemAdding method in the below code uses this technic. This technic is the most common one in .Net Framework class libraries. The danger of this technic is that, because the event is public and everyone can register to it, one of the methods in event handler chain may clear the Cancel property to false and cause the operation be done instead of being cancelled.

Second option is to call each handler, one by one and cancel the operation if one of them sets the Cancel property to true. OnItemRemoving method uses this technic.

C#
public class PlaylistItemsCollectionWithCancellableEvents : IList<PlaylistItem>
{
    List<PlaylistItem> m_Items;

    //Pre-manipulation event, fired before adding an item
    public event PlaylistItemEventHandler ItemAdding;

    //Pre-manipulation event, fired before removing an item
    public event PlaylistItemEventHandler ItemRemoving;

    public PlaylistItemsCollectionWithCancellableEvents()
    {
        m_Items = new List<PlaylistItem>();
    }

    public void Add(PlaylistItem item)
    {
        //Prepare the manipulation operation information
        PlaylistItemEventArgs _args = new PlaylistItemEventArgs(item, ItemOperation.Add);
        
        //Notify the registered listeners about the operation and give chance to cancel it
        OnItemAdding(_args);
        if (_args.Cancel)//Cancel the operation if cancellation is requested
            return;

        m_Items.Add(item);//Add the item to the collection if the operation is not cancelled
        //...
    }

    private void OnItemAdding(PlaylistItemEventArgs e)
    {
        if (ItemAdding != null)
            ItemAdding(this, e);        
    }

    public bool Remove(PlaylistItem item)
    {
        //Same as AddItem operation, but return true/false to inform the caller
        //whether the operation was performed successfully or cancelled

        PlaylistItemEventArgs _args = new PlaylistItemEventArgs(item, ItemOperation.Remove);
        OnItemAdding(_args);
        if (_args.Cancel)
            return false;//Operation was not performed, item was not removed

        m_Items.Remove(item);
        //...

        return true;//Item removed
    }

    private void OnItemRemoving(PlaylistItemEventArgs e)
    {
        Delegate[] _handlers = ItemRemoving.GetInvocationList();
        foreach (PlaylistItemEventHandler handler in _handlers)
        {
            handler(this, e);
            if (e.Cancel)
                return;
        }
    }
    //...
}

The last step is using these events to control item manipulations in the playlist item collection. The sample player class creates item collection and registers to its pre-manipulation events. When an event occures, checks its internal state and cancels the operation if the operation is not allowed at this state.

C#
//In the player class

public class Player
{
    //..
    public Player()
    {       
        m_playlist = new PlaylistItemsCollectionWithCancellableEvents();

        //Register to pre-manipulation events
        m_playlist.ItemAdding += new PlaylistItemEventHandler(m_playlist_ItemAdding);
        m_playlist.ItemRemoving += new PlaylistItemEventHandler(_playlist_ItemRemoving);
        //....
    }
    //...

    private void m_playlist_ItemAdding(object sender, PlaylistItemEventArgs e)
    {
        //Does not allow add operation if the player state is "Playing"
        if (this.State == PlayerState.Playing)
            e.Cancel = true;
    }
}

4 - Hook into the public operations with a hooking interface

This technic resembles the observable-observer pattern in Java which does not have simple event mechanism like in C#. Although this technic is very similar to using events, it may be faster and safer for some scenarios. One may also use this technic in order not to pollute the class interface with many pre-manipulation events.

The first step of this pattern is creating an interface for the operations that will be controlled. In this sample, we want to hook into AddItem and RemoveItem operations.

C#
//Prepare a hooking interface for controlling the desired public operations
public interface IHookingInterface
{
    bool CanAddItem(PlaylistItem item);
    bool CanRemoveItem(PlaylistItem item);
    //...
}

Second step is preparing the controlled class (PlaylistItem collection in this case) which takes an object that implements the hooking interface. Controlled class (PlaylistWithHookablePublicOperations) calls the appropriate methods of the hook interafece before it performs a public operation.

In the below example, m_Hook.CanAddItem is called before performing the real add operation. If the call to m_Hook.CanAddItem returns true, the item is added. If it returns false, it simply does nothing, but sometimes returning false or throwing an exception may be required to tell to caller that the operation was not performed.

C#
public class PlaylistWithHookablePublicOperations : IList<PlaylistItem>
{
    List<PlaylistItem> m_Items;
    IHookingInterface m_Hook;

    //...

    //Get the hook object as a constructor parameter, 
    //it is set by teh owner and not going to change
    public PlaylistWithHookablePublicOperations(IHookingInterface hook)
    {
        m_Hook = hook;
        m_Items = new List<PlaylistItem>();//Inner collection for actually holding the items
    }

    public void Add(PlaylistItem item)
    {
        //Ask to the hook object whether it is ok to add this item
        if (m_Hook.CanAddItem(item))
            m_Items.Add(item);//Add the item if it is allowed
        else
        {
            //Maybe throw exception
        }
    }

    public bool Remove(PlaylistItem item)
    {
        //Ask to the hook object whether it is ok to remove this item
        if (m_Hook.CanRemoveItem(item))
            return m_Items.Remove(item);//Remove the item if it is allowed

        return false;//Item is not removed
    }
    //...
}

The next step is preparing the hook class that implements the hooking interface. These classes are mostly private classes defined in the owner classes. This way, it is possible for them to interact with the owner privately (can access private methods and state).

Do not implement this interface at the owner class itself, always create another small class. Implementing hooking interfaces at the owner classes will pollute their interfaces.

C#
class InnerOrPublicHookClass : IHookingInterface
{
    Player m_Owner;//The owner object whose state will be checked when deciding 
    //whether an operation can be performed

    public InnerOrPublicHookClass(Player owner)
    {
        m_Owner = owner;
    }

    #region IHookingInterface Members

    //Controls the item additions
    public bool CanAddItem(PlaylistItem item)
    {
        //Do not allow the operation if the owner player is at "Playing" state
        if (m_Owner.State == PlayerState.Playing)
            return false;//Tell that operation should be cancelled

        //... other controls if necessary

        return true;//Tell that operation can be done
    }

    //Same as CanAddItem, but control the item removal
    public bool CanRemoveItem(PlaylistItem item)
    {
        if (m_Owner.State == PlayerState.Playing)
            return false;

        //... other controls if necessary

        return true;
    }

    #endregion
}

The last step is passing the hooking obkject to controlled object (the custom, controllable playlist item collection), mostly as a constructor parameter.

C#
public class Player
{
    PlaylistWithHookablePublicOperations m_Playlist;

    public Player()
    {
        m_Playlist = new PlaylistWithHookablePublicOperations(new InnerOrPublicHookClass(this));
    }

    public PlayerState State { get; set; }
}

Although this technic requires a bit more code to write, it is more flexible and perfectly protects the controlled class from unwanted manipulations.

History

  • 16 May 2017 - Initial version.
  • 22 May 2017 - Read-only indexer option is added to the first method "Expose the playlist items as read-only collection and provide manipulation methods in the playlist class". Thanks to George Swan.

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Freelance
Sweden Sweden
Experienced senior C# developer, sometimes codes in Java and C++ also. He designs and codes desktop applications (WinForms, WPF), windows services and Web APIs (WCF, ASP.Net MVC). He is currently improving his ASP.Net MVC and JavaScript capabilities. He acts as an architect, coder and trainer.
He is mostly experienced in Media Asset Management (MAM) applications, broadcasting sector and medical applications.

LinkedIn: www.linkedin.com/mustafa-kok/
GitHub: github.com/nthdeveloper
StackOverflow: stackoverflow.com/users/1844220/nthdeveloper

Comments and Discussions

 
QuestionSpelling Pin
BinaryReason4-Jun-18 4:54
BinaryReason4-Jun-18 4:54 
QuestionHave you considered Indexers? Pin
George Swan16-May-17 9:54
mveGeorge Swan16-May-17 9:54 
AnswerRe: Have you considered Indexers? Pin
Mustafa Kok21-May-17 22:47
professionalMustafa Kok21-May-17 22:47 
Yes, this is another way of creating a read only collection. I will add this option to the first part as another way. Thank you for your contribution.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.