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

Kolejna część code review .NET Core. Po poszukiwaniach wzorców kreacyjnych tym razem wzorce strukturalne. Podobnie jak poprzednio, wklejone przykłady nie zawierają pełnych definicji klas, a jedynie istotne fragmenty.

Adapter

Celem jest dopasowanie istniejącej klasy do innego interfejsu. 

[DebuggerTypeProxy(typeof(System.Collections.ArrayList.ArrayListDebugView))]
[DebuggerDisplay("Count = {Count}")]
public class ArrayList : IList
{        

    public static ArrayList Adapter(IList list)
    {
        if (list == null)
            throw new ArgumentNullException(nameof(list));
        Contract.Ensures(Contract.Result() != null);
        Contract.EndContractBlock();
        return new IListWrapper(list);
    }

    // This class wraps an IList, exposing it as a ArrayList
    // Note this requires reimplementing half of ArrayList...
    private class IListWrapper : ArrayList
    {
        private IList _list;

        internal IListWrapper(IList list)
        {
            _list = list;
            _version = 0;
        }

        public override int Count
        {
            get { return _list.Count; }
        }

        public override bool IsReadOnly
        {
            get { return _list.IsReadOnly; }
        }
    }

}

Interesuje nas, by typ implementujący IList pasował do ArrayList. Klasa ArrayList wystawia statyczną metodę Adapter, która instancjonuje niefortunnie się nazywającą klasę IListWrapper. Ta z kolei wystawia wszystkie potrzebne metody i properties dla ArrayList.

Decorator

Rozszerzanie właściwości obiektów.

public class CryptoStream : Stream, IDisposable
{
    // Member variables
    private readonly Stream _stream;
    private readonly ICryptoTransform _transform;
    
    // Constructors

    public CryptoStream(Stream stream, ICryptoTransform transform, CryptoStreamMode mode)
    {

        _stream = stream;
        _transformMode = mode;
        _transform = transform;
        switch (_transformMode)
        {
            case CryptoStreamMode.Read:
                if (!(_stream.CanRead)) throw new ArgumentException(SR.Format(SR.Argument_StreamNotReadable, "stream"));
                _canRead = true;
                break;
            case CryptoStreamMode.Write:
                if (!(_stream.CanWrite)) throw new ArgumentException(SR.Format(SR.Argument_StreamNotWritable, "stream"));
                _canWrite = true;
                break;
            default:
                throw new ArgumentException(SR.Argument_InvalidValue);
        }
        InitializeBuffer();
    }
}

Mamy klasę CryptoStream, która rozszerza Stream (w tym przypadku szyfrując). Klasa zarówno dziedziczy po typie Stream jak i wymaga jego implementacji jako parametr w jedynym konstruktorze.

Facade

Pojedyncza klasa tworzy warstwę abstrakcji upraszczając inne API. 

internal sealed class RNGCryptoServiceProvider : RandomNumberGenerator
{
    public sealed override void GetBytes(byte[] data)
    {
        ValidateGetBytesArgs(data);
        if (data.Length > 0)
        {
            Interop.AppleCrypto.GetRandomBytes(data, data.Length);
        }
    }
}

Źródła .NET Core zawierają trzy implementacje tej jakże ważnej z punktu widzenia kryptografii klasy (dla Windows, Linux i zamiszczony powyżej OSX). Użyta klasa AppleCrypto woła pod spodem niezarządzany kod.oraz zawiera wywołanie metody za pomocą mechanizmu P/Invoke.

Proxy

Obiekt, który reprezentuje inny obiekt.

internal class LambdaExpressionProxy
{
    private readonly LambdaExpression _node;

    public LambdaExpressionProxy(LambdaExpression node)
    {
        _node = node;
    }

    public Expression Body { get { return _node.Body; } }
    public Boolean CanReduce { get { return _node.CanReduce; } }
    public String DebugView { get { return _node.DebugView; } }
    public String Name { get { return _node.Name; } }
    public ExpressionType NodeType { get { return _node.NodeType; } }
    public ReadOnlyCollection Parameters { get { return _node.Parameters; } }
    public Type ReturnType { get { return _node.ReturnType; } }
    public Boolean TailCall { get { return _node.TailCall; } }
    public Type Type { get { return _node.Type; } }
}

W przestrzeni System.Diagnostics mamy atrybut DebuggerProxyType, który można umieścić nad definicją każdej klasy. Jako argument podajemy, który typ ma służyć za proxy dla tej klasy i być wyświetlany w debuggerze po najechaniu muszą na symbol (musi przyjmować oryginalny typ w konstruktorze). Sam .NET też wykorzystuje ten mechanizm, powyżej definicja typu proxy dla klasy LambdaExpression.

Composite

Obiekt, reprezentujący strukturę innych obiektów.

namespace System.Xml
{
    public abstract class XmlNode : IEnumerable
    {
        internal XmlNode parentNode;

        public virtual XmlNodeList ChildNodes
        {
            get { return new XmlChildNodes(this); }
        }

        public virtual XmlNode PreviousSibling
        {
            get { return null; }
        }

        public virtual XmlNode NextSibling
        {
            get { return null; }
        }

        public virtual XmlNode FirstChild
        {
            get
            {
                XmlLinkedNode linkedNode = LastNode;
                if (linkedNode != null)
                    return linkedNode.next;

                return null;
            }
        }

        public virtual string InnerXml
        {
            get
            {
                StringWriter sw = new StringWriter(CultureInfo.InvariantCulture);
                XmlDOMTextWriter xw = new XmlDOMTextWriter(sw);
                try
                {
                    WriteContentTo(xw);
                }
                finally
                {
                    xw.Dispose();
                }
                return sw.ToString();
            }

            set
            {
                throw new InvalidOperationException(SR.Xdom_Set_InnerXml);
            }
        }
    }
}

Przykładem kompozytu jest klasa XmlNode, reprezentująca pojedynczy węzeł dokumentu XML. Property InnerXml wystawia nam hierarchiczną strukturę węzłów. Odpowiedzialnością tej właśnie klasy jest, aby klient wołając to property otrzymał całą hierarchię, niezależnie od liczby zagnieżdżeń.

Kolejny, ostatni już odcinek, dotyczący wzorców behawioralnych już za tydzień.

Advertisements

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