// ========================================================================
//
// Copyright (C) 2016-2017 MARC CHEVALDONNE
//                         marc.chevaldonne.free.fr
//
// Module        : MonLit2.cs
// Author        : Marc Chevaldonné
// Creation date : 2016-09-30
//
// ========================================================================

using System;
using System.Collections.Generic;

namespace ex_023_016_ReadOnlyCollection
{
    /// <summary>
    /// L'exemple suivant montre comment encapsuler une collection d'un type dans les deux cas principaux suivants :
    /// 1) le type est immuable (en façade au moins), c'est-à-dire qu'on ne peut pas modifier son état interne en appelant ses méthodes ou propriétés
    /// 2) le type est mutable, ie que l'utilisateur peut modifier l'état interne de ses instances, et notre API l'autorise.
    /// Comment encapsuler correctement une collection d'un tel type ?
    /// Dans l'exemple suivant, le type Nounours2 est mutable, nous voulons protéger la collection contre :
    /// - les modifications de la collection (ajout ou suppression d'éléments),
    /// - la modification de la collection elle-même (on ne doit pas pouvoir faire de new)
    /// Nous ne cherchons pas à protéger les éléments de la collection (en particulier si le type n'est pas immuable).
    /// En résumé, nous cherchons à rendre publique en lecture seule, la collection mNounours2 privée de MonLit2.
    /// </summary>
    class MonLit2
    {
        //on veut encapsuler cette collection d'un type immuable
        private List<Nounours2> mNounours2 = new List<Nounours2>(new Nounours2[] {
                                    new Nounours2(1, "Beluga", new DateTime(2012, 07, 29), 0),
                                    new Nounours2(2, "Singe", new DateTime(2009, 08, 09), 1345),
                                    new Nounours2(2, "Truite", new DateTime(2013, 04, 3), 0),
                                    new Nounours2(3, "Girafe", new DateTime(2007, 11, 02), 567)});

        /// <summary>
        /// Tentative 1 (mauvaise) :
        /// Ceci est absolument très très mauvais. La propriété semble en lecture seule car le setter est privé, mais la List est type référence.
        /// Certes, on ne peut pas faire Nounours2List = new List<Nounours2>(); car le setter est privé, 
        /// mais on peut faire par exemple Nounours2List[i] = new Nounours2(...);
        /// ou bien, Nounours2List.Clear();, Nounours2List.RemoveAt(i); Nounours2List.Add(...);
        /// ...
        /// Cette solution ne protège que du new.
        /// </summary>
        public List<Nounours2> Nounours2List
        {
            get
            {
                return mNounours2;
            }
            private set
            {
                mNounours2 = value;
            }
        }

        /// <summary>
        /// Tentative 2 (moins mauvaise, mais mauvaise quand même) :
        /// On a l'impression, en utilisant le type de plus haut niveau, ici IEnumerable, d'empêcher toute action. 
        /// Malheureusement, un utilisateur de notre classe, mais s'il n'est pas mal intentionné, peut tenter :
        /// var temp = (Nounours2IEnumerable as List<Nounours2>);
        /// temp.Add(...);
        /// temp.Clear();
        /// ...
        /// Il peut donc faire les mêmes dégâts que précédemment, même si le fait d'avoir un IEnumerable<Nounours2> devrait l'inciter à ne pas le faire.
        /// </summary>
        public IEnumerable<Nounours2> Nounours2IEnumerable
        {
            get
            {
                return mNounours2;
            }
        }

        /// <summary>
        /// Tentative 3 (quasi-identique à la précédente) :
        /// On a l'impression, en utilisant le type de plus haut niveau, ici IReadOnlyCollection, d'empêcher toute action. 
        /// Malheureusement, un utilisateur de notre classe, mais s'il n'est pas mal intentionné, peut tenter :
        /// var temp = (Nounours2IROC as List<Nounours2>);
        /// temp.Add(...);
        /// temp.Clear();
        /// ...
        /// Il peut donc faire les mêmes dégâts que précédemment, même si le fait d'avoir un IEnumerable<Nounours2> devrait l'inciter à ne pas le faire.
        /// </summary>
        public IReadOnlyCollection<Nounours2> Nounours2IROC
        {
            get
            {
                return mNounours2;
            }
        }

        //parfait pour encapsuler un type immuable 
        /// <summary>
        /// Solution (la bonne !)
        /// Ici, on déclare une collection ReadOnlyCollection : il s'agit d'un wrapper autour de la collection mNounours2.
        /// Ce wrapper est initialisé dans le constructeur de MonLit.
        /// Le setter privé de Nounours2ROC empêche de réinitialiser le wrapper (pas d'appel du new).
        /// ReadOnlyCollection<Nounours2> empêche l'appel des méthodes Add, Remove, Clear...
        /// On ne peut pas caster Nounours2ROC en List<Nounours2> car il ne s'agit pas de la même instance
        /// (Nounours2ROC n'est pas la même référence que mNounours2, un de ses membres la référence privée mNounours2).
        /// 
        /// Avantage énorme : tous les changements de mNounours2 sont directement répercutés sur Nounours2ROC puisqu'il l'enveloppe.
        /// 
        /// Attention : si la référence mNounours2 change (appel de new ou de =), alors Nounours2ROC n'enveloppe plus mNounours2, mais l'ancienne collection mNounours2.
        /// </summary>
        public System.Collections.ObjectModel.ReadOnlyCollection<Nounours2> Nounours2ROC
        {
            get;
            private set;
        }

        public MonLit2()
        {
            Nounours2ROC = new System.Collections.ObjectModel.ReadOnlyCollection<Nounours2>(mNounours2);
        }

        /// <summary>
        /// Variante de la solution
        /// Cette solution donne les mêmes résultats que la précédente à quelques différences près :
        /// - avantage : pas besoin d'initialiser dans le constructeur ou de réinitialiser si la référence mNounours2 change (appel de new ou =)
        ///   => celle-ci semble donc mieux que la précédente
        /// - inconvénient : à chaque fois qu'on appelle Nounours2ROC2, on recrée un nouveau wrapper.
        ///   => la solution précédente semble donc moins coûteuse en appel de méthodes, même s'il est vrai que AsReadOnly ne coûte pas très cher.
        /// Conclusion : choisissez l'une des deux solutions, comme vous voulez !
        /// </summary>
        public System.Collections.ObjectModel.ReadOnlyCollection<Nounours2> Nounours2ROC2 => mNounours2.AsReadOnly();

    }
}
