dotnet 寫一個支持層層繼承屬性的對象


我最近在造一個比 Excel 差得多的表格控件,其中一個需求是屬性的繼承。大家都知道,表格里面有單元格,單元格里面允許放文本,文本可以放多段文本。本文的主角就是文本段落的樣式屬性,包括文本字體字號顏色等等屬性。文本段落的屬性,如果沒有特別設置,將使用單元格里面的文本樣式屬性。而如果單元格里面,沒有特別指定此單元格使用特殊的文本樣式,將會繼承使用當前所在的行的文本樣式。如果當前行沒有特殊指定文本樣式屬性,那么將會使用文檔的默認樣式。文檔默認樣式將會根據是否有特殊指定而采用主題樣式

如此復雜的層層繼承邏輯,如果每個屬性都需要自己一層層去尋找,那代碼量將會特別多。維護起來就想吃桌子

為了保住桌子,咱來寫一個支持層層繼承屬性的對象。如在當前層找不到某個屬性,將會往上一層自動去找,一層層找。如果都找不到,將返回默認值

以下是這個類的定義代碼

    public class FlattenObject
    {
        /// <summary>
        /// 創建帶繼承的對象
        /// </summary>
        /// <param name="reserved"></param>
        public FlattenObject(FlattenObject? reserved = null)
        {
            Reserved = reserved;
        }

        private FlattenObject? Reserved { get; }
        private Dictionary<string, object> ValueDictionary { get; } = new Dictionary<string, object>();

        /// <summary>
        /// 設置屬性值
        /// </summary>
        /// <param name="value"></param>
        /// <param name="propertyName"></param>
        protected void SetValue(object value, [CallerMemberName] string propertyName = null!)
        {
            ValueDictionary[propertyName] = value;
        }

        /// <summary>
        /// 獲取屬性值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        protected T? GetValue<T>([CallerMemberName] string propertyName = null!)
            => GetValue<T>(default!, propertyName);

        /// <summary>
        /// 獲取屬性值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="defaultValue"></param>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        protected T GetValue<T>(T defaultValue, [CallerMemberName] string propertyName = null!)
        {
            if (ValueDictionary.TryGetValue(propertyName, out var value))
            {
                return (T) value;
            }
            else
            {
                if (Reserved is not null)
                {
                    return Reserved.GetValue<T>(defaultValue, propertyName);
                }
                else
                {
                    return defaultValue;
                }
            }
        }
    }

通過 Reserved 屬性表示的是當前層的上一層的對象,用於給當前層進行繼承。因為每一層都包含了上一層的對象,因此從最下層就可以一層層自動找到屬性的值

繼承當前類型,即可寫出下面代碼

        class FooFlattenObject : FlattenObject
        {
            public FooFlattenObject(FlattenObject reserved = null) : base(reserved)
            {
            }

            public string FontName
            {
                set => SetValue(value);
                get => GetValue<string>();
            }

            public int Count
            {
                set => SetValue(value);
                get => GetValue<int>();
            }
        }

如上面代碼,在各個屬性的 set 和 get 都換成調用方法,而不需要定義字段

下面來嘗試寫單元測試

            "給定可繼承的對象,可以從繼承的對象拿到屬性值".Test(() =>
            {
                var reserved = new FooFlattenObject()
                {
                    FontName = "微軟雅黑"
                };

                var fakeFlattenObject = new FooFlattenObject(reserved);
                Assert.AreEqual("微軟雅黑", fakeFlattenObject.FontName);

                fakeFlattenObject.Count = 2;
                Assert.AreEqual(2, fakeFlattenObject.Count);
                Assert.AreEqual(0, reserved.Count);
            });

可以看到在 reserved 對象里面設置了 FontName 的值,可以被 fakeFlattenObject 繼承拿到,同時自動讀取的代碼對於上層業務來說幾乎沒有

對 fakeFlattenObject 進行設置 Count 的值,不會影響到 reserved 對象

通過此方法可以讓存在層層繼承邏輯的代碼不需要大量重復。除了在表格上使用,也可以用在如解析 PPT 的形狀內文本,如 PPT 的圖片裁剪等需要繼承屬性的邏輯上

上面的代碼也存在不足,那就是對於結構體不友好,如 bool 或 int 等類型,都需要轉換為 object 存放


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM