Advertisement
Guest User

Untitled

a guest
Nov 13th, 2019
135
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.34 KB | None | 0 0
  1. using System;
  2. using System.Collections.Generic;
  3.  
  4. namespace Equalizery
  5. {
  6. // Nasza POCO klasa (POCO to "Plain Old CLR Object", jest to termin używany dla klas-wydmuszek które nie mają żadnej logiki (ani atrybutów) a tylko przechowują dane, termin ten dotyczy tylko .NET, w Javie mają POJO)
  7. public class Message
  8. {
  9. // Możemy również zedytować operator przyrównania (jak i wiele innych, np. "operator tablicowy" czyli to co maluch ostatnio zdziałal, this[])
  10. public static bool operator ==(Message a, Message b)
  11. => a != null && a.Equals(b);
  12.  
  13. // Jeżeli implementujemy (overridujemy) operator == to *zawsze* musimy zaimplementować również operator !=
  14. // hasło "!(a == b)" jest najczęstszą (99% przypadków) implementacją operatora !=, wszak != musi zawsze dać przeciwną wartość co == . Zauważ że "a != b" nie zadziała bo się zapętlimy, ponieważ przecież właśnie ten operator tutaj implementujemy
  15. public static bool operator !=(Message a, Message b)
  16. => !(a == b);
  17.  
  18. public Guid MessageId { get; set; }
  19. public string SenderName { get; set; }
  20. public string ReceiverName { get; set; }
  21. public string Text { get; set; }
  22. public int Priority { get; set; }
  23. public DateTime IssuedAt { get; set; }
  24.  
  25. // Equals w klasie to poważna sprawka, jak Equals jest true to znaczy że obiekty są identiko, rzadko kiedy overriduje się Equals które nie sprawdza *wszystkich* elementów, ale czasem może się zdarzyć
  26. // Jak się overriduje Equals to powinno się również zoverridować GetHashCode (i vice versa) ponieważ obie te metody operują na "unikalności", jeżeli dwa obiekty generują inny hashcode, to nigdy nie powinny być równe (nigdy Equals ani operator == nie powinien dać true)
  27. public override bool Equals(object obj)
  28. {
  29. if (obj is Message msg)
  30. {
  31. return MessageId == msg.MessageId
  32. && SenderName == msg.SenderName
  33. && ReceiverName == msg.ReceiverName
  34. && Text == msg.Text
  35. && Priority == msg.Priority
  36. && IssuedAt == msg.IssuedAt;
  37. }
  38.  
  39. return false;
  40. }
  41.  
  42. // tutaj robimy małego trika, zamiast bawić się w konkatenację hashcodów poszczególnych elementów, robimy wielkiego tupla z wszystkiego i wyciągamy jego hashcode
  43. // a on pod spodem sobie sam z tym jakoś radzi (tutaj przychodzą z pomocą duże tuple, a Dadi tak kłamczył że nigdy się ich nie używa, nieładnie)
  44. public override int GetHashCode()
  45. => (MessageId, SenderName, ReceiverName, Text, Priority, IssuedAt).GetHashCode();
  46.  
  47. // Jest dobrą praktyką by implementować całe quatro, tj. operator "==" oraz "!=" wraz z overridem na Equals i GetHashCode, jednak często ludzie jak już muszą robić override, to olewają operator i elo.
  48. // Często ludzie w ogóle nie overridują Equalsa tylko robią jakieś pseudometody albo unikają porównywania, czasem jest to też overhead bo zobacz jaki bałagan się w tej klasie teraz zrobił
  49. }
  50.  
  51. public class MessageSenderComparer : IEqualityComparer<Message>
  52. {
  53. // komparerki są wolne od wszelkich konwencji i możemy sobie sprawdzać co chcemy i jak chcemy, wszak będą one obowiązywać tylko tam gdzie sami zadecydujemy że chcemy ich użyć, taki Equals na przykład działa na całą aplikację i dlatego powinno się tam sprawdzać wszystko
  54. public bool Equals(Message x, Message y)
  55. {
  56. if (x is null)
  57. return y is null;
  58. return !(y is null)
  59. && x.SenderName == y.SenderName;
  60. }
  61.  
  62. // IEqualityComparer posiada również osobną metodkę na GetHashCode z tego samego powodu co opisywałem wyżej, GetHashCode również służy do porównań i gdy dwa obiekty mają ten sam hashcode, powinny zwracać true w przypadku .Equals
  63. public int GetHashCode(Message obj)
  64. => obj.SenderName.GetHashCode();
  65. }
  66.  
  67. public class MessageSender
  68. {
  69. // ostatnio jestem wielkim fanem "readonly", powoli sprawiam by ten mój gałgan z pracy też był wielkim fanem
  70. private readonly int messagesToSend;
  71. private readonly HashSet<Message> messages;
  72.  
  73. public MessageSender(string name, int messagesToSend, bool oneMessagePerSender = true)
  74. {
  75. Name = name;
  76.  
  77. // "this" ponieważ field i parametr nazywają się tak samo
  78. this.messagesToSend = messagesToSend;
  79.  
  80. // możemy wybrać czy używamy defaultowego comparera (który operuje na .Equals/.GetHashCode) czy wrzucimy własnego który sprawdzi tylko SenderName
  81. // polityka wrzucania nowego obiektu comparera jest bardzo często spotykana i jest to spoko, inną opcją może być gdy coś prosi o obiekt typu Type, i wtedy sobie sam wewnętrznie robi obiekt w oparciu o ten typ.
  82. // tj. mogłoby być "new HashSet<Message>(typeof(MessageSenderComparer))", tak często robią atrybuty ponieważ jak wiadomo, nie przekażemy obiektu do atrybutu ponieważ atrybut jest compile-time.
  83. // taki HashSet trzyma sobie wtedy wewnątrz taki obiekt comparera, i jak przychodzi moment sprawdzenia (np. przy dodaniu nowej wartości) to sobie używa jego metody Equals, więc ten comparer jest takim daj-mi-logikę-a-nie-dane obiektem
  84. // całkowicie odbiegając od tematu, tak jak to mam w zwyczaju, obiekt danego typu mając do dyspozycji tylko i wyłącznie obiekt Type możemy zrobić korzystając z klasy Activator, np tak: "(MessageSenderComparer)Activator.CreateInstance(typeof(MessageSenderComparer));"
  85. messages = oneMessagePerSender
  86. ? new HashSet<Message>(new MessageSenderComparer())
  87. : new HashSet<Message>();
  88. }
  89.  
  90. public event EventHandler<IEnumerable<Message>> MessagesSentHandler;
  91.  
  92. public string Name { get; } // "readonly" property
  93.  
  94. // HashSet zwraca true/false w zależności od tego czy udało się dodać obiekt czy nie (czy już taki istnieje czy nie)
  95. public bool AddMessage(Message message)
  96. {
  97. if (!messages.Add(message))
  98. {
  99. // w rzeczywistości ten writeline (logger) powinien być wykonany wyżej, tam gdzie metoda AddMessage jest użyta.
  100. // AddMessage zwraca daną na temat tego czy udało się dodać czy nie (true/false), jest to w gestii serwisu który zarządza tą klasą by tę informację odpowiednio przetworzyć
  101. Console.WriteLine($"Nie udało się dodać wiadomości od użytkownika {message.SenderName}!");
  102. return false;
  103. }
  104.  
  105. // jeżeli w naszym secie znajduje się dostateczna ilość wiadomości, "wysyłamy" je poprzez notyfikację wszystkich zainteresowanych naszym eventem (so many things at once, ależ to Dadi skonstruował)
  106. if (messages.Count == messagesToSend)
  107. {
  108. Console.WriteLine($"Osiągneliśmy wymagane {messagesToSend} wiadomości do wysyłki, wiadomości wysłane!");
  109. MessagesSentHandler?.Invoke(this, messages); // odpalamy event, ofc z nullcheckiem w razie gdyby nie było zainteresowanych
  110. messages.Clear(); // czyścimy listę i zaczynamy zbierać wiadomki od nowa
  111. }
  112.  
  113. return true;
  114. }
  115. }
  116.  
  117. public class Program
  118. {
  119. public static void Main(string[] args)
  120. {
  121. var allMessagesSender = new MessageSender("WszystkieSender", 3, false);
  122. allMessagesSender.MessagesSentHandler += AllMessagesSender_MessagesSentHandler;
  123.  
  124. var uniqueMessagesSender = new MessageSender("OnePerSenderSender", 3);
  125. uniqueMessagesSender.MessagesSentHandler += AllMessagesSender_MessagesSentHandler;
  126.  
  127. Console.WriteLine(allMessagesSender.AddMessage(GenerateMessage("Agiczi", "Kapi"))); // 1-wsza wiadomość
  128. Console.WriteLine(allMessagesSender.AddMessage(GenerateMessage("Hubert Urbański", "Robert Makłowicz"))); // 2-ga
  129. Console.WriteLine(allMessagesSender.AddMessage(GenerateMessage("Agiczi", "Józef Stalin"))); // 3-cia, wszystko przeszło ponieważ ten Sender operuje na defaultowym comparerku, a ta wiadomość różni się od 1-wszej receiverem
  130. // dingdong allMessagesSender odpalił iwent
  131.  
  132. Console.WriteLine("--------------------------------");
  133.  
  134. Console.WriteLine(uniqueMessagesSender.AddMessage(GenerateMessage("Agiczi", "Kapi"))); // 1-wsza wiadomość
  135. Console.WriteLine(uniqueMessagesSender.AddMessage(GenerateMessage("Hubert Urbański", "Robert Makłowicz"))); // 2-ga
  136. Console.WriteLine(uniqueMessagesSender.AddMessage(GenerateMessage("Agiczi", "Józef Stalin"))); // 3-cia, nie przeszła ponieważ Comparer który został wrzucony do HashSet'u postanowił że taka wartość już istnieje, i ją odrzucił
  137. Console.WriteLine(uniqueMessagesSender.AddMessage(GenerateMessage("Jakaś Zdzira", "Maluch"))); // 4-ta (a tak naprawdę 3-cia), takiego sendera jeszcze HashSet nie miał więc wszystko przeszło
  138. // uniqueMessagesSender dostał 3-cią wiadomkę, i odpalił event
  139.  
  140. Console.WriteLine("--------------------------------");
  141. }
  142.  
  143. private static void AllMessagesSender_MessagesSentHandler(object sender, IEnumerable<Message> messages)
  144. {
  145. if (sender is MessageSender messageSender)
  146. {
  147. foreach (var message in messages)
  148. {
  149. Console.WriteLine($"\"{messageSender.Name}\" [{message.IssuedAt}]{{{message.Priority}}} '{message.SenderName}'->'{message.SenderName}': {message.Text}");
  150. }
  151. }
  152. }
  153.  
  154. // ta metodka mogłaby być konstruktorem Message, ale nie chciałem już tam bałaganić
  155. public static Message GenerateMessage(string sender, string receiver)
  156. => new Message
  157. {
  158. MessageId = Guid.NewGuid(),
  159. SenderName = sender,
  160. ReceiverName = receiver,
  161. Text = "hejka hejka",
  162. Priority = 1,
  163. IssuedAt = DateTime.UtcNow,
  164. };
  165. }
  166. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement