主頁 > 軟體設計 > ECS架構分析

ECS架構分析

2023-03-07 09:02:16 軟體設計

概述

ECS全稱Entity-Component-System,即物體-組件-系統,是一種面向資料(Data-Oriented Programming)的編程架構模式,
這種架構思想是在GDC的一篇演講《Overwatch Gameplay Architecture and Netcode》(翻成:守望先鋒的游戲架構和網路代碼)后受到了廣泛的學習討論,在代碼設計上有一個原則“組合優于繼承”,它的核心設計思想是基于這一思想的“組件式設計”,

ECS的基本型別

image

  • Entity(物體):在ECS架構中表示“一個單位”,可以被ECS內部標識,可以掛載若干組件,
  • Component(組件):掛載在Entity上的組件,負載物體某部分的屬性,是純資料結構不包含函式,
  • System(系統):純函式不包含資料,只關心具有某些特定屬性(組件)的Entity,對這些屬性進行處理,

運行邏輯

某個業務系統篩選出擁有這個業務系統相關組件的物體,對這些物體的相關組件進行處理更新,

基本特點

Entity資料結構抽象:

PosiComp MoveComp AttrComp ...
Pos Velocity Hp ...
Map - Mp ...
- - ATK ...
  • 組件內聚本業務相關的屬性,某個物體不同業務的屬性通過組件聚合在一起,
    • 從資料結構角度上看,Entity類似一個2維的稀疏表,如上述Entity資料結構抽象
    • OOP的思路知道型別就知道了這個物件的屬性,ECS的物體是知道了有哪些組件知道這個物體大概是什么,有點像鴨子理論:如果走路像鴨子、說話像鴨子、長得像鴨子、啄食也像鴨子,那它肯定就是一只鴨子,
  • 業務系統收集所有具有本業務要求組件的Entity,集中批量的處理這些Entity的相關組件

推論

  • ECS的組件式設計,是高內聚、低耦合的,對千變萬化的業務需求十分友好
  • 批量處理資料在這些資料在連續記憶體的場合下對CPU快取機制友好
  • 低資料耦合可以減少資源競爭對并行友好
  • ECS處理資料的方式是批量處理的,一個物體需要連續處理的場合十分不友好

個人見解

個人認為ECS架構的核心是為了解決物件中復雜的聚合問題,能有效的管理代碼的復雜度,至于某些場合下的性能的提升,在大多數情況下只是錦上添花的作用(一些SLG游戲具有大量單位可能會有提升吧),它沒有傳統OOP編程模式的復雜的繼承關系造成的不必要的耦合,結構更加扁平化,相比之下更易于業務的閱讀理解和拓展,但這種技術并非是完美無缺的,它十分不擅長單個物體需要連續處理業務(如序列化等)或物體之間相互關聯等場合(如更新兩個物體的距離),而且對于一些業務邏輯相對固定的模塊或者一些底層模塊來說,松耦合和管理復雜度可能不是首要問題,有可能在設計上硬拗ECS組件式設計反而帶來困擾,對于游戲來說,ECS架構在GamePlay上的實用程度相對較高,在其他符合其特性的模塊如(網路模塊)也能提供一些不同以往的解題思路,

細節討論

單例組件

Q:有些資料只需要一份或被全域訪問等情況下,沒必要掛載在Entity上和篩選
A:使用單例組件,和其他組件一樣是純資料,但是可以通過單例全域訪問,即可以被任意系統任意訪問,

工具方法

Q:有些處理方法,不適合進行批量處理(例如計算兩個單位的距離,沒必要弄個系統每個單位都相互計算距離)
A:用工具方法,它通常是無副作用的,不會改變任何狀態,只回傳計算結果

System之間的依賴關系

Q: 假設有渲染系統和碰撞系統,要像在這一幀正確的渲染目標的位置,就需要碰撞系統先更新位置資訊,渲染系統在進行位置,需要正確處理系統間的前后依賴關系,

A:一個很自然的思路就是分層,根據不同層級的優先級進行處理,由此提出流水線(Piepline)的抽象,定義一顆樹和相關節點,系統掛載在其節點上,運行時以某種順序(先序遍歷)展開,同一個節點的系統可以并行(沒有依賴),有需要的話流水線還可以定義系統/物體/組件的初始化等其他問題,

System對Entity的篩選

Q:“原教旨主義”的ECS框架有ECS幀的概念,系統會在每一幀重新篩選需要處理的Entity,這種處理方式引起了很大的爭論,大家認為是有一些優化空間,

A:社區中幾乎沒人贊同“原教旨主義”的做法,原因很簡單:很多Entity在整個生命周期中都沒組件的增刪操作,還有相當部分有的有增刪操作的Entity其操作頻率也很低,每幀都遍歷重新篩選代價相對太過昂貴,所以有人提出了快取、分類、延遲增刪操作等思路,一種思路是:Entity的增刪/組件的增刪的操作進行快取,延遲到該系統運行時在進行評估篩選,以減少遍歷和重復操作,

Entity是否在運行期動態更改組件分類&System是否每幀篩選Entity分類

Q:并不是每個Entity運行期都會改變動態變更組件,有些Entity在運行期壓根就不變更組件,甚至它只被編譯期就知道的指定System處理,也有些System不在運行期篩選Entity,要么編譯期就知道處理哪些Entity,要么是處理一些單例組件,所以有人提出要不要對Entity和System對它們是否在運行期動態操作進行分類,以提升效率,

A:個人認為,Entity不變更組件,本身變動訊息就很少只有增刪,配合一些快取、延遲篩選等方法其實沒什么影響,不動態篩選Entity的System倒是可以分型別關閉Entity篩選,

是否加入回應式處理

Q:ECS是“自驅式”的更新,就像是U3D的Mono的Update方法更新,還有一種回應式的更新,即基于訊息事件的通知,“原教旨主義”式的ECS框架是完全自驅的,沒有訊息機制,系統之間“訊息傳遞”是通過組件的資料傳遞的,所以在處理“當進入地圖時”這種場合,只能使用“HasEnterMap”或者“Enum.EnterMap”之類的標簽,或者添加一個“EnterMapComponent"來處理,

A:個人傾向于加入一些訊息的處理機制,可以更靈活些,基本思路是:給System添加一個收件箱,收到的訊息放在收件箱的佇列里,Entity相關變更(增刪、變更組件)的一些訊息單獨使用一個佇列管道,在系統重繪的時候首先處理Entity變更訊息,進行評估篩選Entity,然后處理信箱里的其他訊息,然后在處理System的更新邏輯,

記憶體效率優化

Q:批量處理資料在物理記憶體連續的場合有利于CPU快取機制,關鍵是如何讓資料的記憶體連續,首先想到的是使用陣列,那么是組件使用陣列還是Entity使用陣列呢?
A:如果是組件使用陣列,那么當系統處理的Entity包含多個組件的話,那么記憶體訪問會在不同的陣列中“跳來跳去”,優化效果十分有限,個人認為若是一定要優化記憶體訪問,關鍵是保證組件一樣的Entity存放在連續記憶體(Chuck)中,這樣保證System訪問Entity的記憶體連續,具體實作方案可以參考U3D的ECS設計Archetype和Chuck,另外,也有物件池的優化空間,上面提到,ECS并不是主要解決性能問題的,只是順帶的,不必太過于執著,當然有也是極好的~,

Unity ECS引入了Archetype和Chuck兩個概念,Archetype即為Entity對應的所有組件的一個組合,然后多個Archetypes會打包成一個個Archetype chunk,按照順序放在記憶體里,當一個chunck滿了,會在接下來的記憶體上的位置創建一個新的chunk,因此,這樣的設計在CPU尋址時就會更容易找到Entity相關的component

image

原型Demo示例

using System;
using System.Collections.Generic;
using System.Threading;

namespace ECSDemo
{
    public class Singleton<T> where T : Singleton<T>, new()
    {
        private static T inst;

        public static T Inst
        {
            get
            {
                if (inst == null)
                    inst = new T();
                return inst;
            }
        }
    }

    #region Component 組件
    public class Component
    {

    }

    public class SingleComp<T> : Singleton<T> where T : Singleton<T>, new()
    {
        //
    }
    #endregion 

    #region Entity 物體

    public class EntityFactory
    {
        static long eid = 0;

        public static Entity Create()
        {
            Entity e = new Entity(eid);
            eid++;
            EntityChangedMsg.Inst.Pub(e);
            return e;
        }

        public static Entity CreatePlayer()
        {
            var e = Create();
            e.AddComp(new PosiComp());
            e.AddComp(new NameComp() { name = "Major" });
            return e;
        }

        public static Entity CreateMonster(string name)
        {
            var e = Create();
            e.AddComp(new PosiComp());
            e.AddComp(new NameComp() { name = name });

            return e;
        }
    }

    public class Entity
    {
        long instID = 0;

        public long InstID { get => instID; }

        public Entity(long id) { instID = id; }

        // 預計一個Entity組件不會很多,故使用鏈表...
        List<Component> comps = new();

        public void AddComp<T>(T t) where T : Component
        {
            comps.Add(t);
            EntityChangedMsg.Inst.Pub(this);
        }

        public void RemoveComp<T>(T t) where T : Component
        {
            comps.Remove(t);
            EntityChangedMsg.Inst.Pub(this);
        }

        public T GetComp<T>() where T : Component
        {
            foreach (var comp in comps)
                if (comp is T) return comp as T;

            return default(T);
        }

        public bool ContrainComp(Type type)
        {
            foreach (var comp in comps)
                if (comp.GetType() == type) return true;
            return false;
        }
    }
    #endregion

    #region System 系統
    public class System
    {
        protected SystemMsgBox msgBox = new();

        public virtual void Run()
        {
            msgBox.Each();
            OnRun();
        }

        public virtual void OnRun()
        {

        }
    }

    public class SSystem : System
    {
        //
    }

    public class DSystem : System
    {
        protected Dictionary<long, Entity> entities = new();
        protected List<Type> conds = new();
        HashSet<Entity> evalSet = new();

        public DSystem()
        {
            msgBox.Sub(EntityChangedMsg.Inst, (msg) => {
                var body = (EntityChangedMsg.MsgBody)msg;
                var e = body.Value;
                evalSet.Add(e);
            });
        }

        public void Evalute(Entity e)
        {
            var id = e.InstID;
            bool test = true;
            foreach (var cond in conds)
                if (!e.ContrainComp(cond))
                {
                    test = false;
                    break;
                }

            Entity cache;
            entities.TryGetValue(id, out cache);
            if (test)
                if (cache == null) entities.Add(id, e);
                else
                if (cache != null) entities.Remove(id);
        }

        public override void Run()
        {
            msgBox.EachEntityMsg();
            foreach (var e in evalSet)
                Evalute(e);
            evalSet.Clear();
            msgBox.Each();
            OnRun();
        }
    }
    #endregion

    #region Pipline 流水線
    public class Pipeline<ENode, V>
    {
        public class Node<NENode, NV>
        {
            List<NV> items = new();
            NENode node;
            Node<NENode, NV> parent;
            List<Node<NENode, NV>> childern = new();

            public List<Node<NENode, NV>> Childern { get => childern; }
            public List<NV> Items { get => items; }

            public Node(NENode n)
            {
                node = n;
            }

            public void AddChild(Node<NENode, NV> c)
            {
                childern.Add(c);
                c.parent = this;
            }

            public void RemoveChild(Node<NENode, NV> c)
            {
                childern.Remove(c);
                c.parent = null;
            }

            public void AddItem(NV v)
            {
                items.Add(v);
            }

            public void RemoveItem(NV v)
            {
                items.Remove(v);
            }

        }

        Node<ENode, V> root;
        Dictionary<ENode, Node<ENode, V>> dict = new();
        public Pipeline(ENode node)
        {
            root = new Node<ENode, V>(node);
            dict.Add(node, root);
        }

        public void AddNode(ENode n)
        {
            Node<ENode, V> p = root;
            AddNode(n, p);
        }

        public void AddNode(ENode n, Node<ENode, V> p)
        {
            var node = new Node<ENode, V>(n);
            p.AddChild(node);
            dict.Add(n, node);
        }

        public void AddNode(ENode n, ENode p)
        {
            Node<ENode, V> node;
            dict.TryGetValue(p, out node);
            if (node != null)
                AddNode(n, node);
        }

        public void AddItem(ENode n, V item)
        {
            Node<ENode, V> node;
            dict.TryGetValue(n, out node);
            if (node != null)
                node.AddItem(item);
        }

        public void RemoveItem(ENode n, V item)
        {
            Node<ENode, V> node;
            dict.TryGetValue(n, out node);
            if (node != null)
                node.RemoveItem(item);
        }

        protected void Traveral(Action<V> action)
        {
            TraveralInner(root, action);
        }

        protected void TraveralInner(Node<ENode, V> node, Action<V> action)
        {
            var childern = node.Childern;
            var items = node.Items;
            foreach (var child in childern)
                TraveralInner(child, action);
            foreach (var item in items)
                action(item);
        }
    }

    public class SystemPipeline : Pipeline<ESystemNode, System>
    {
        public SystemPipeline(ESystemNode en) : base(en)
        {
            //
        }

        public void Update()
        {
            Traveral((sys) => sys.Run());
        }
    }

    public enum ESystemNode : int
    {
        Root = 0,
        Base = 1,
        FrameWork = 2,
        GamePlay = 3,
    }

    
    #endregion

    #region World 世界
    public class World : Singleton<World>
    {
        SystemPipeline sysPipe;

        public void Init()
        {
            sysPipe = SystemPipelineTemplate.Create();
        }

        public void Update()
        {
            sysPipe.Update();
        }
    }
    #endregion

    #region Event 事件
    public class Event<T> : Singleton<Event<T>>
    {
        List<Action<T>> actions = new();

        public void Sub(Action<T> action)
        {
            actions.Add(action);
        }

        public void UnSub(Action<T> action)
        {
            actions.Remove(action);
        }

        public void Pub(T t)
        {
            foreach (var action in actions)
                action(t);
        }
    }

    public class EveEntityChanged : Event<Entity> { }

    public interface IMsgBody
    {
        Type Type();
    }

    public interface IMsg
    {
        void Sub(MsgBox listener);
        void UnSub(MsgBox listener);
    }

    public class Msg<T> : Singleton<Msg<T>>, IMsg
    {
        public class MsgBody : IMsgBody
        {
            public MsgBody(T v, Type ty) { Value = https://www.cnblogs.com/hggzhang/archive/2023/03/06/v; type = ty; }
            Type type;
            public T Value { private set; get; }

            public Type Type()
            {
                return type;
            }
        }

        List listeners = new();

        public void Sub(MsgBox listener)
        {
            listeners.Add(listener);
        }

        public void UnSub(MsgBox listener)
        {
            listeners.Remove(listener);
        }

        public void Pub(T t)
        {
            var msgBody = new MsgBody(t, this.GetType());
            foreach (var listener in listeners)
                listener.OnMsg(msgBody);
        }
    }

    public class EntityChangedMsg : Msg { }

    public class MsgBox
    {
        protected Queue msgs = new();
        protected Dictionary> handles = new();

        public virtual void OnMsg(IMsgBody body)
        {
            msgs.Enqueue(body);
        }


        public void Sub(IMsg msg, Action cb)
        {
            msg.Sub(this);
            handles.Add(msg.GetType(), cb);
        }

        public void UnSub(IMsg msg, Action cb)
        {
            msg.UnSub(this);
            handles.Remove(msg.GetType());
        }

        public virtual void Each()
        {
            while (msgs.Count != 0)
            {
                var msg = msgs.Dequeue();
                var type = msg.Type();
                Action handle;
                handles.TryGetValue(type, out handle);
                if (handle != null)
                    handle(msg);
            }
        }
    }

    public class SystemMsgBox : MsgBox
    {
        Queue entityMsgs = new();

        public override void OnMsg(IMsgBody body)
        {
            if (body.Type() == typeof(EntityChangedMsg))
                entityMsgs.Enqueue(body);
            else
                msgs.Enqueue(body);
        }

        public void EachEntityMsg()
        {
            while (entityMsgs.Count != 0)
            {
                var msg = entityMsgs.Dequeue();
                var type = msg.Type();
                Action handle;
                handles.TryGetValue(type, out handle);
                if (handle != null)
                    handle(msg);
            }
        }

        public override void Each()
        {
            while (msgs.Count != 0)
            {
                var msg = msgs.Dequeue();
                var type = msg.Type();
                Action handle;
                handles.TryGetValue(type, out handle);
                if (handle != null)
                    handle(msg);
            }
        }
    }

    #endregion

    #region AppTest

    public class AppComp : SingleComp
    {
        public bool hasInit;
    }

    public class MapComp : SingleComp
    {
        public bool hasInit;
        public int monsterCnt = 2;
    }

    public class PosiComp : Component
    {
        public int x;
        public int y;
    }

    public class NameComp : Component
    {
        public string name ="";
    }

    public class AppSystem : SSystem
    {
        public override void OnRun()
        {
            if (!AppComp.Inst.hasInit)
            {
                AppComp.Inst.hasInit = true;
                Console.WriteLine("App 啟動");
            }
        }
    }

    public class SystemPipelineTemplate
    {
        public static SystemPipeline Create()
        {
            SystemPipeline pipeline = new(ESystemNode.Root);

            // 基本系統
            pipeline.AddNode(ESystemNode.Base, ESystemNode.Root);
            pipeline.AddItem(ESystemNode.Base, new AppSystem());

            pipeline.AddNode(ESystemNode.GamePlay, ESystemNode.Root);
            pipeline.AddItem(ESystemNode.GamePlay, new PlayerSystem());
            pipeline.AddItem(ESystemNode.GamePlay, new MapSystem());

            return pipeline;
        }
    }

    public class MapSystem : DSystem
    {
        public MapSystem() : base()
        {
            conds.Add(typeof(PosiComp));
            conds.Add(typeof(NameComp));
        }

        public override void OnRun()
        {
            if (!MapComp.Inst.hasInit)
            {
                MapComp.Inst.hasInit = true;
                for (int i = 0; i < MapComp.Inst.monsterCnt; i++)
                    EntityFactory.CreateMonster($"Monster{i + 1}");

                Console.WriteLine($"進入地圖 生成{MapComp.Inst.monsterCnt}只小怪");
            }

            foreach (var (id, e) in entities)
            {
                var name = e.GetComp<NameComp>().name;
                var x = e.GetComp<PosiComp>().x;
                var y = e.GetComp<PosiComp>().y;

                Console.WriteLine($"【{name}】 在地圖的 x = {x}, y = {y}");

            }
        }
    }

    public class PlayerComp : SingleComp<PlayerComp>
    {
        public Entity Major;
    }

    public class PlayerSystem : SSystem
    {
        public override void OnRun()
        {
            base.OnRun();
            if (PlayerComp.Inst.Major == null)
                PlayerComp.Inst.Major = EntityFactory.CreatePlayer();

            if (Console.KeyAvailable)
            {
                int dx = 0;
                int dy = 0;
                ConsoleKeyInfo key = Console.ReadKey(true);
                switch (key.Key)
                {
                    case ConsoleKey.A:
                        dx = -1;
                        break;
                    case ConsoleKey.D:
                        dx = 1;
                        break;
                    case ConsoleKey.W:
                        dy = 1;
                        break;
                    case ConsoleKey.S:
                        dy = -1;
                        break;


                    default:
                        break;
                }

                if (dx != 0 || dy != 0)
                {
                    var comp = PlayerComp.Inst.Major.GetComp<PosiComp>();
                    if (comp != null)
                    {
                        Console.WriteLine($"玩家移動 Delta X = {dx}, Delta Y = {dy}");
                        comp.x += dx;
                        comp.y += dy;
                    }
                }
            }
        }
    }

    #endregion

    class Program
    {
        static void Main(string[] args)
        {
            World.Inst.Init();
            while (true)
                Loop();
        }

        public static void Loop()
        {
            World.Inst.Update();
            Console.WriteLine("--------------------------------------------");
            Thread.Sleep(1000);
        }
    }
}

  • Demo包含了ECS的基本定義和分層、篩選、訊息等機制,簡單的原型多看下應該可以看明白,
  • 當XXX的訊息使用組件的資料HasInit實作,當然也可以使用訊息,思路是:給System加虛函式Awake、Start、End、Destory等虛函式,SystemPipeline初始化時兩次遍歷分別Awake、Start,同樣,清理時兩次遍歷呼叫End、Destory函式,可以在Start時監聽一些訊息,在End時清理,
  • Pipeline流水線有一種更加自動化的系結節點的方法:使用C#的特性(Attribute)標記System,在程式啟動通過反射自動組裝,大概類似這樣:
[AttributeUsage(AttributeTargets.Class)]
public class SystemPipelineAttr : Attribute
{
    public ESystemNode Type;

    public SystemPipelineAttr(Type type = null)
    {
        this.Type = type;
    }
}

[SystemPipelineAttr(ESystemNode.GamePlay)]
public class MapSystem {} // ...

// ...
public static Dictionary<string, Type> GetAssemblyTypes(params Assembly[] args)
{
        Dictionary<string, Type> types = new Dictionary<string, Type>();

        foreach (Assembly ass in args)
        {
            foreach (Type type in ass.GetTypes())
            {
                types[type.FullName] = type;
            }
        }

        return types;
}

// ...
foreach (Type type in types[typeof (SystemPipelineAttr)])
{
	object[] attrs = type.GetCustomAttributes(typeof(SystemPipelineAttr), false);
	foreach (object attr in attrs)
	{
		SystemPipelineAttr attribute = attr as SystemPipelineAttr;
		// ...
	}
}

備注

  • ECS的架構目前使用的非常的多,很多有名的框架設計都或多或少的受到了其影響,有:
    • U3D的ECS架構:不是指原來的GameObj那套,有專門的插件,有記憶體優化
    • UE4的組件設計:采用了特殊的組件實作父子關系
    • ET框架:訊息 + ECS,采用ECS解耦,更注重訊息驅動的回應式設計,Entity和Comp的思路也獨特:Entity同時是組件,并有父子關系
    • 云風大佬的引擎:好像未開源,只有一些blog在討論ECS,貌似連引擎層面和Lua側都涉及ECS的設計思想

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/545987.html

標籤:其他

上一篇:軟體產品實施模式之集裝箱搭建蓋樓模式

下一篇:Git倉庫遷移實操(附批量遷移腳本)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more