Wzorce projektowe w źródłach .NET: 3/3 wzorce behawioralne

Ostatnia część code review źródeł .NET Core. Przeglądaliśmy już wzorce kreacyjne i strukturalne. Tym razem poszukujemy wzorców behawioralnych.

Warto pamiętać, że słynna książka Gang of Four na temat  wzorców została wydana w roku 1994, natomiast początki .NET to rok 2002. Dlatego też niektóre wzorce, takie jak iterator czy observer zostały wbudowane w sam C#. Mam tu na myśli konstrukcje takie jak yield czy event. Poszukajmy zatem pozostałych wzorców behawioralnych.

Null Object

Reprezentacja domyślnej wartości danego obiektu.

namespace System.IO
{
    public abstract class Stream : IDisposable
    {
        public static readonly Stream Null = new NullStream();
        //...

        private sealed class NullStream : Stream
        {
            internal NullStream() { }

            public override bool CanRead
            {
                [Pure]
                get
                { return true; }
            }
           
            public override long Position
            {
                get { return 0; }
                set { }
            }            

            protected override void Dispose(bool disposing)
            {
                // Do nothing - we don't want NullStream singleton (static) to be closable
            }

            public override int Read(byte[] buffer, int offset, int count)
            {
                return 0;
            }
        }
    }
}

Klasa NullStream jest Singletonem i ma za zadanie reprezentować pusty strumień. Większość przeładowanych metod z klasy bazowej albo nic nie robi albo zwraca domyślne wartości. Dobry sposób na zabezpieczenie się przed wyjątkami typu NullReference.

Command

Reprezentacja żądań w postaci obiektów.

namespace System.Data
{
    using System;

    public interface IDbCommand : IDisposable
    {

        IDbConnection Connection
        {
            get;
            set;
        }

        string CommandText
        {
            get;
            set;
        }

        //........

        IDbDataParameter CreateParameter();

        int ExecuteNonQuery();

        IDataReader ExecuteReader();

        IDataReader ExecuteReader(CommandBehavior behavior);

        object ExecuteScalar();
    }
}

Interfejs reprezentuje komendy wysyłane do bazy danych. W zależności od rodzaju komendy i oczekiwanych rezultatów możemy wywołać jedną z czterech metod Execute

Template Method

Implementację kolejnych kroków algorytmu odkładamy do klas pochodnych.

namespace System.Collections
{
    // Useful base class for typed read/write collections where items derive from object
    public abstract class CollectionBase : IList
    {        

        Object IList.this[int index]
        {
            get
            {
                //.....
            }
            set
            {
                if (index < 0 || index >= Count)
                    throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index);
                Contract.EndContractBlock();
                OnValidate(value);
                Object temp = InnerList[index];
                OnSet(index, temp, value);
                InnerList[index] = value;
                try
                {
                    OnSetComplete(index, temp, value);
                }
                catch
                {
                    InnerList[index] = temp;
                    throw;
                }
            }
        }

        protected virtual void OnSet(int index, Object oldValue, Object newValue)
        {
        }

        protected virtual void OnRemove(int index, Object value)
        {
        }

        protected virtual void OnValidate(Object value)
        {
            if (value == null) throw new ArgumentNullException(nameof(value));
            Contract.EndContractBlock();
        }

        protected virtual void OnSetComplete(int index, Object oldValue, Object newValue)
        {
        }
    }
}

Ważna klasa CollectionBase zawiera indekser. W setterze tego indeksera mamy krótki algorytm, który krok po kroku woła wirtualne metody, z którch większość ma pustą implementację w tej klasie bazowej. Dziedzicząc po CollectionBase możemy nadpisać te metody według własnych potrzeb. Wszystko w przestrzeni nazw System.Collections, czyli jeszcze sprzed epoki generyków.

Strategy

Enkapsulacja algorytmu wewnątrz klasy.

namespace System.Security.Cryptography
{
    public abstract class SymmetricAlgorithm : IDisposable
    {
        public virtual byte[] Key
        {
            get
            {
                if (KeyValue == null)
                    GenerateKey();
                return KeyValue.CloneByteArray();
            }

            set
            {
                if (value == null)
                    throw new ArgumentNullException(nameof(value));

                long bitLength = value.Length * 8L;
                if (bitLength > int.MaxValue || !ValidKeySize((int)bitLength))
                    throw new CryptographicException(SR.Cryptography_InvalidKeySize);

                // must convert bytes to bits
                this.KeySize = (int)bitLength;
                KeyValue = value.CloneByteArray();
            }
        }
        
        public virtual ICryptoTransform CreateDecryptor()
        {
            return CreateDecryptor(Key, IV);
        }

        public abstract ICryptoTransform CreateDecryptor(byte[] rgbKey, byte[] rgbIV);

        public virtual ICryptoTransform CreateEncryptor()
        {
            return CreateEncryptor(Key, IV);
        }

        public abstract ICryptoTransform CreateEncryptor(byte[] rgbKey, byte[] rgbIV);
    }
}

Klasa bazowa dla całej rodziny algorytmów szyfrowania symetrycznego. Dziedziczą po niej takie klasy jak Aes czy TripleDes. Możemy też teoretycznie napisać swój algorytm szyfrowania symetrycznego, a zamieszczona powyżej klasa zawiera wspólne API dla całej tej rodziny algorytmów.

Visitor

Definiuje nową operację dla klasy bez zmiany tej klasy.

namespace System.Linq.Expressions
{
    /// <summary>
    /// Represents a dynamic operation.
    /// </summary>
    public class DynamicExpression : Expression, IDynamicExpression
    {
        private readonly CallSiteBinder _binder;
        private readonly Type _delegateType;

        internal DynamicExpression(Type delegateType, CallSiteBinder binder)
        {
            Debug.Assert(delegateType.GetMethod("Invoke").GetReturnType() == typeof(object) || GetType() != typeof(DynamicExpression));
            _delegateType = delegateType;
            _binder = binder;
        }

        /// <summary>
        /// Dispatches to the specific visit method for this node type.
        /// </summary>
        protected override Expression Accept(ExpressionVisitor visitor)
        {
            var dynVisitor = visitor as DynamicExpressionVisitor;
            if (dynVisitor != null)
            {
                return dynVisitor.VisitDynamic(this);
            }

            return base.Accept(visitor);
        }
    }
}

Metoda Accept udostępnia swój stan wszystkim klasom dziedziczącym po ExpressionVisitor. Ich zadaniem jest wykonanie operacji na obiekcie typu DynamicExpression. Metoda Accept wołając wizytora przekazuje referencję do samego siebie  (this).

I tym postem zamykamy trzyodcinkową serię udało się znaleźć aż 15 różnych wzorców w źródłach coreclr.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s