Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- using System;
- using System.Collections.Generic;
- namespace Equalizery
- {
- // 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)
- public class Message
- {
- // Możemy również zedytować operator przyrównania (jak i wiele innych, np. "operator tablicowy" czyli to co maluch ostatnio zdziałal, this[])
- public static bool operator ==(Message a, Message b)
- => a != null && a.Equals(b);
- // Jeżeli implementujemy (overridujemy) operator == to *zawsze* musimy zaimplementować również operator !=
- // 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
- public static bool operator !=(Message a, Message b)
- => !(a == b);
- public Guid MessageId { get; set; }
- public string SenderName { get; set; }
- public string ReceiverName { get; set; }
- public string Text { get; set; }
- public int Priority { get; set; }
- public DateTime IssuedAt { get; set; }
- // 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ć
- // 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)
- public override bool Equals(object obj)
- {
- if (obj is Message msg)
- {
- return MessageId == msg.MessageId
- && SenderName == msg.SenderName
- && ReceiverName == msg.ReceiverName
- && Text == msg.Text
- && Priority == msg.Priority
- && IssuedAt == msg.IssuedAt;
- }
- return false;
- }
- // 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
- // 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)
- public override int GetHashCode()
- => (MessageId, SenderName, ReceiverName, Text, Priority, IssuedAt).GetHashCode();
- // 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.
- // 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ł
- }
- public class MessageSenderComparer : IEqualityComparer<Message>
- {
- // 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
- public bool Equals(Message x, Message y)
- {
- if (x is null)
- return y is null;
- return !(y is null)
- && x.SenderName == y.SenderName;
- }
- // 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
- public int GetHashCode(Message obj)
- => obj.SenderName.GetHashCode();
- }
- public class MessageSender
- {
- // ostatnio jestem wielkim fanem "readonly", powoli sprawiam by ten mój gałgan z pracy też był wielkim fanem
- private readonly int messagesToSend;
- private readonly HashSet<Message> messages;
- public MessageSender(string name, int messagesToSend, bool oneMessagePerSender = true)
- {
- Name = name;
- // "this" ponieważ field i parametr nazywają się tak samo
- this.messagesToSend = messagesToSend;
- // możemy wybrać czy używamy defaultowego comparera (który operuje na .Equals/.GetHashCode) czy wrzucimy własnego który sprawdzi tylko SenderName
- // 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.
- // 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.
- // 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
- // 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));"
- messages = oneMessagePerSender
- ? new HashSet<Message>(new MessageSenderComparer())
- : new HashSet<Message>();
- }
- public event EventHandler<IEnumerable<Message>> MessagesSentHandler;
- public string Name { get; } // "readonly" property
- // HashSet zwraca true/false w zależności od tego czy udało się dodać obiekt czy nie (czy już taki istnieje czy nie)
- public bool AddMessage(Message message)
- {
- if (!messages.Add(message))
- {
- // w rzeczywistości ten writeline (logger) powinien być wykonany wyżej, tam gdzie metoda AddMessage jest użyta.
- // 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ć
- Console.WriteLine($"Nie udało się dodać wiadomości od użytkownika {message.SenderName}!");
- return false;
- }
- // 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ł)
- if (messages.Count == messagesToSend)
- {
- Console.WriteLine($"Osiągneliśmy wymagane {messagesToSend} wiadomości do wysyłki, wiadomości wysłane!");
- MessagesSentHandler?.Invoke(this, messages); // odpalamy event, ofc z nullcheckiem w razie gdyby nie było zainteresowanych
- messages.Clear(); // czyścimy listę i zaczynamy zbierać wiadomki od nowa
- }
- return true;
- }
- }
- public class Program
- {
- public static void Main(string[] args)
- {
- var allMessagesSender = new MessageSender("WszystkieSender", 3, false);
- allMessagesSender.MessagesSentHandler += AllMessagesSender_MessagesSentHandler;
- var uniqueMessagesSender = new MessageSender("OnePerSenderSender", 3);
- uniqueMessagesSender.MessagesSentHandler += AllMessagesSender_MessagesSentHandler;
- Console.WriteLine(allMessagesSender.AddMessage(GenerateMessage("Agiczi", "Kapi"))); // 1-wsza wiadomość
- Console.WriteLine(allMessagesSender.AddMessage(GenerateMessage("Hubert Urbański", "Robert Makłowicz"))); // 2-ga
- 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
- // dingdong allMessagesSender odpalił iwent
- Console.WriteLine("--------------------------------");
- Console.WriteLine(uniqueMessagesSender.AddMessage(GenerateMessage("Agiczi", "Kapi"))); // 1-wsza wiadomość
- Console.WriteLine(uniqueMessagesSender.AddMessage(GenerateMessage("Hubert Urbański", "Robert Makłowicz"))); // 2-ga
- 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ł
- 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
- // uniqueMessagesSender dostał 3-cią wiadomkę, i odpalił event
- Console.WriteLine("--------------------------------");
- }
- private static void AllMessagesSender_MessagesSentHandler(object sender, IEnumerable<Message> messages)
- {
- if (sender is MessageSender messageSender)
- {
- foreach (var message in messages)
- {
- Console.WriteLine($"\"{messageSender.Name}\" [{message.IssuedAt}]{{{message.Priority}}} '{message.SenderName}'->'{message.SenderName}': {message.Text}");
- }
- }
- }
- // ta metodka mogłaby być konstruktorem Message, ale nie chciałem już tam bałaganić
- public static Message GenerateMessage(string sender, string receiver)
- => new Message
- {
- MessageId = Guid.NewGuid(),
- SenderName = sender,
- ReceiverName = receiver,
- Text = "hejka hejka",
- Priority = 1,
- IssuedAt = DateTime.UtcNow,
- };
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement