-
-
Save INTERNALINTERFERENCE/9a385e5b65321e8973971d8ea1c61a2d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Trie : ViewModelBase | |
{ | |
public Trie( | |
string part, | |
int level, | |
Trie? root = null, | |
Trie? parent = null ) | |
{ | |
Part = part; | |
Level = level; | |
Parent = parent; | |
Root = root ?? this; | |
MessagesCount = 1; | |
TopicsCount = 0; | |
History | |
.Connect() | |
.ObserveOn( RxApp.TaskpoolScheduler ) // to run on background thread | |
.Sort( SortExpressionComparer<ReceivedHistoryMessage> | |
.Descending( e => e.Id ) ) | |
.ObserveOn( RxApp.MainThreadScheduler ) //this needed because UI updates need to run on the main | |
.Bind( out _uiHistoryMessages ) | |
.DisposeMany() | |
.Subscribe(); | |
Observable | |
.Interval( TimeSpan.FromSeconds( 1 ) ) | |
.Subscribe( _ => | |
{ | |
Root.EnqueueUpdate(); | |
} ); | |
} | |
#region IO | |
public SourceList<ReceivedHistoryMessage> History { get; } = new(); | |
private readonly ReadOnlyObservableCollection<ReceivedHistoryMessage> _uiHistoryMessages; | |
private readonly Dictionary<string, Trie> _trieChildren = new(); | |
private AvaloniaList<Trie> _visibleChildren = new(); | |
public IAvaloniaReadOnlyList<Trie> VisibleChildren | |
=> _visibleChildren; | |
public ReadOnlyObservableCollection<ReceivedHistoryMessage> UiHistoryMessages | |
=> _uiHistoryMessages; | |
[Reactive] private int TopicsCount { get; set; } | |
[Reactive] private int MessagesCount { get; set; } | |
[Reactive] public bool IsShowMessagesChecked { get; set; } | |
[Reactive] public string? Header { get; set; } | |
[Reactive] public string? Payload { get; set; } | |
[Reactive] public string? Topic { get; set; } | |
[Reactive] public MqttDelivery Delivery { get; set; } = new(); | |
public string Part { get; } | |
public int Level { get; } | |
public Trie? Parent { get; } | |
private Trie Root { get; } | |
private bool _updateEnqueued; | |
private int _historyId; | |
private bool _isExpanded; | |
public bool IsExpanded | |
{ | |
get => _isExpanded; | |
set | |
{ | |
this.RaiseAndSetIfChanged( ref _isExpanded, value ); | |
Root.EnqueueUpdate(); | |
} | |
} | |
#endregion IO | |
public bool Add( | |
string[] topic, | |
int level, | |
MqttDelivery delivery ) | |
{ | |
var changedTopicCount = false; | |
if ( topic.IsNullOrEmpty() ) | |
{ | |
Delivery = delivery; | |
Topic = delivery.Topic; | |
Header = delivery.Header?.ToJsonString( true ); | |
Payload = delivery.Payload?.ToJsonString( true ); | |
TopicsCount = 1; | |
AddToHistory( delivery ); | |
return false; | |
} | |
MessagesCount++; | |
if ( !_trieChildren.ContainsKey( topic[ 0 ] ) ) | |
{ | |
var node = new Trie( topic[ 0 ], level, Root, this ); | |
_trieChildren.TryAdd( topic[ 0 ], node ); | |
changedTopicCount = true; | |
} | |
if ( !_trieChildren[ topic[ 0 ] ] | |
.Add( topic.Skip( 1 ).ToArray(), level + 1, delivery ) | |
&& !changedTopicCount ) | |
return changedTopicCount; | |
changedTopicCount = true; | |
TopicsCount++; | |
return changedTopicCount; | |
} | |
public void EnqueueUpdate() | |
{ | |
if ( _updateEnqueued ) return; | |
_updateEnqueued = true; | |
Dispatcher.UIThread.Post( | |
Update, | |
DispatcherPriority.Background ); | |
} | |
private void AppendItems() | |
{ | |
_updateEnqueued = false; | |
var flatTrieList = new AvaloniaList<Trie>(); | |
AppendItems( | |
flatTrieList, | |
this ); | |
_visibleChildren = flatTrieList; | |
} | |
private void AppendItems( | |
AvaloniaList<Trie> flatTrieList, | |
Trie node ) | |
{ | |
flatTrieList.Add( node ); | |
if ( !node.IsExpanded | |
|| IsShowMessagesChecked ) return; | |
foreach ( var ch in node._trieChildren ) | |
AppendItems( flatTrieList, ch.Value ); | |
} | |
private void Update() | |
{ | |
AppendItems(); | |
Root.RaisePropertyChanged( nameof( Root.VisibleChildren ) ); | |
} | |
public void ForceResync() | |
{ | |
Root.Update(); | |
} | |
private void AddToHistory( MqttMessage mqttMessage ) | |
{ | |
while ( History.Count >= 43 ) | |
History.RemoveAt( History.Count - 1 ); | |
History.Insert( 0, new ReceivedHistoryMessage | |
{ | |
Id = _historyId++, | |
Time = DateTime.Now, | |
Message = mqttMessage | |
} ); | |
} | |
} |
спасибо вам, получилось красиво
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Обещал написать здесь про рефакторинг этого класса.
Что плохо? Основное, на что хочется обратить внимание: потенциально возможное нарушение целостности дерева извне. У вас есть единственный публичный конструктор:
То есть фактически я могу извне создать поддерево с произвольным корнем
root
, указать какого-то родителяparent
и вообще не факт, что у этого родителя будет тот же самый корень. Всё, структура дерева "развалена". Зачем вообще передавать в конструктор корень, если его и так знает родитель? :)Но ещё лучше будет вообще запретить извне создавать поддеревья и навешивать их на какие-то узлы. Только ваш класс знает правильную логику "сборки" дерева, так пусть только он и создает новые поддеревья для "верхних" узлов. Значит, конструктор
Trie(string part, int level, Trie? parent)
делаем приватным, в извне можно создать только корень дерева каким-нибудь новым публичным конструкторомpublic Trie(string part) : this(part, 0, null) {}
.Но можно сделать ещё чуть понятнее ваш код. Сейчас вы выделяете только один класс -
Trie
, который фактически представляет собой узел дерева и, по идеи, его лучше переименовать вTrieNode
. Почти для всех деревьев, отражающих структуру чего-либо, важно не потерять корень дерева, ибо нормальные операции вставки и поиска должны выполняться от корня. Обычной практикой является выделение отдельного класса для представления дерева целиком (а это то же самое, что представлять корень дерева). Например, переименуем вашTrie
вTrieNode
и добавим такой классTrie
(илиTrieRoot
, чтобы не было путаницы с тем, что у вас сейчас):Тогда мы можем переопределить конструкторы базового
TrieNode
например так:Теперь пользоваться деревом гораздо проще: мы не можем извне создать инстанс
TrieNode
– их будет создавать сам ваш класс по вызовамAdd()
, но можем создатьTrieRoot
и знать, что это всегда корень дерева. Как и всеTrieNode
всегда будут знать, где их корень.Ну и ещё несколько замечаний.
Зачем вам метод
Update()
? Перенесите его код вForceResync()
и всё.С методом
Add
тоже стоит разобраться. Исходить нужно из того, должна ли в вашем дереве присутствовать логика добавления поддерева НЕ в корень извне класса? Если нет – делайте метод приватным, и реализуйте публичныйAdd
только для вставки из корня без параметраlevel
(с передачейlevel: 0
в приватныйAdd
). Если да – всё равно убирайте параметрlevel
из публичной реализации, тогда у вас получится что-то вроде:Кстати, в методе
Add
вместоreturn changedTopicCount;
лучше писатьreturn true
илиreturn false
– вы ведь в каждой точке кода и так знаете на этапе компиляции, чему у вас равноchangedTopicCount
:) От последнегоchangedTopicCount = true;
тогда можно вообще избавиться, например.Наконец, я уже писал об этом вам, лучше конечно разделять логику алгоритма дерева и логику представления данных. Подумайте о том, чтобы
IsExpanded
у вас вызывало просто какой-тоNotify()
, а этот сторонний класс уже получал список список раскрытых узлов и кастил его к UI-шному (?)AvaloniaList<T>
. Тогда вам можно избавиться от всех этихForceResync
,Update
иAppendItems
и оставить в классе только свойство VisibleChildren с такой, например, реализацией:А сторонний класс уже при получении Notify от вашего свойства
IsExpanded
будет обращаться кVisibleChildren
от корня (?) и перерисовывать что нужно. В идеале - вашTrieNode
вообще ничего не должен знать о классах UI.Надеюсь, что эти идеи окажутся полезными для вас.