środa, 28 października 2009

ASD Polemic

This post is a polemic with
this. This is why it's in English.

Just to provide enough context, I'll quote a lecture slide (I hate you for not doing this in your post; I've wasted so much time trying to figure out what do you mean, e.g. what is the factory supposed to create or where is your point of view in the system that you develop).
The lecture slide says:
"
In some situations, using the new keyword to construct
new objects of a given class is NONO
Connection con = new Connection(db);
This suffers from tight coupling. If, later on, you want
to issue connections from a pre-existing pool of
objects, rather than creating a new one every time, this
code prevents you. Instead, use a 'factory' approach:
Connection con = Connection.get(db);
"
First lets make things clear.
First of all note that this example is there to making people aware that there exists a thing called a factory method and that it may help when sharing objects. When an noob reader reads this slide, he grasps those two ideas and everything's OK, because those general ideas are OK. On the other hand, when someone who really knows something about those things read this, he will almost certainly say "ugh!", because it is not a good example of factory. This is a bit useless pattern called factory method, because:
1. This method is defined when the Connection class gets written, noone can change it later
2. Noone can create objects of the subclasses of Connection (assuming constructors are private).
3. It may give you the same object many times when you don't want that.
4. It is a static method so it in general cannot rely on having any information about context in which it is called.

The example on the slide is really bad because a method of class A creates objects of class A, which makes the problems 1 and 2 very true, so it is even worse than using the constructor directly. If it was another class, which would allow this method to be non-static, you could probably inherit from it, redefine the method and get the desired behaviour.
ASIDE: Note that I do not say "using new operator", but "using constructor" to demonstrate how much I dislike the fact that the lectures are so Java-oriented.

What overcomes all those problems is the abstract factory pattern.

After a tough but thorough analysis of your stream of conciousness I arrived at a conclusion that you are actually arguing agains the above constraints of the factory method pattern.
You correctly argue with the problems 1 and 3 in about half of your post, starting with:
"For example, say you have a database connection object and you want to set up a pool of X objects that you want to reuse. Rather than use the factory method, you could edit the Connection object such that is composed of e.g. a DatabaseSocket which interfaces with the database directly. Connection's constructor grabs a DatabaseSocket from the pool, and existing methods on the Connection object forward themselves to the DatabaseSocket. (delegation!)"
You basically found an example in which you need to modify the way Connection is defined with (pardon my English (as French say) if it's not a comprehensible sentence). Additionally, you could probably have "interface Connection" and a subclass of it defined to solve your specific problem with multithreading. This would overcome problem 3.
You argue with the problem 4 in:
"Consider the case where there is contention for a limited resource. With a factory method that directly hands you an object, the onus is usually on you to properly handle sharing and locking for that object."
A proper object oriented factory could handle that because you would define the way of creating objects and you could use available information about how to share and lock particular objects. You would just give that knowledge as an argument to the constructor of the factory. Now I should describe the abstract factory design pattern but other people have done it before, so I will not repeat the effort. You can find it on Wikipedia.

"'do you want a brand new object of your very own, or are you happy being given an object to achieve what you want?' If you're doing anything with mutable state outside your program logic, you probably don't need to track any extra data outside of whatever exists externally, and you should ask whatever subsystem you are dealing with for appropriate objects to this effect. However, if you want additional data with your ice-cream sundae, the former is actually a sensible thing to do, and doesn't preclude you from changing the implementation of the created class so that it grabs the 'real' object elsewhere behind the scenes."
Very true. However, note that with the abstract factory you can pretty much have the advantages of both things, so if any one of the above solutions is too limited, there is another way!
To conclude, you are generally right, but you are a bit cherry-picking a particular example from the slide and you are missing the point a bit, as you praise a normal idea agains a bad one, when there is a really good one (the abstract factory), that solves what the bad one was supposed to solve.

---
Different issue:
"Inheritance has its uses, but you need to be careful about what it really means. It's not about extending functionality as much as it is about telling people "Hey, I have a cool object here with special features I can use, but since it behaves exactly like this more basic object, you guys get to play with the same instance as me!"."
So you are talking about extending the inheritance hierarchy upwards (introducing another subclass such that "you guys" (i.e. another code) can start using the object). This is one way of taking advantage of inheritance and it is a very good one because we build up the hierarchy after we actually have an implementation of a base class. Therefore we know what polimorphic methods will be needed and what are necessary generality constrains of the base class to make the implementation feasible. This usually leads to introducing something that Java calls an interface. Note, however, that if a class is really designed to extend it (e.g. because it is part of a framework), then extending functionality can be a good idea as well. It is really important, however, to distinguish between extending existing functionality and/or data (not a good idea in most cases) and implementing interface.
The main problem of inheritance is CONFUSION, MISUSE and MIXING of those two ideas. Using any one of them is not a problem. The whole idea of inheritance is also not a big problem, on the contrary to what Ian tries to squash into students' minds (Yes, I know that inheritance is sometimes inflexible because you cannot add methods to base classes, but this is not the argument that Ian uses. At least he stresses it an order of magnitude weaker than all the other arguments).

The lecture slide says:
"The extends word wrongly suggests "add more fields
to" or "reuse the implementation of""

I think that there is a big problem with naming, because "inheritance" really does not describe a result of using a programming concept, but way of implementing both of the two concepts described above by a programming language. That's why in C++, which is quite old now, there is syntactically just one way of inheriting (the ":" operator; OK, you can write "private", "protected" or "virtual" after it but nobody uses the first two and the third is broken). In Java, which is a bit younger, there is a separation of extending classes and implementing interfaces. There is a problem with the extending mechanism, particularly with the illusion of being able to extend anything and override every method of it, but the "extends" keyword is a good word. It reminds the writer that it whatever is extended should really be extendable. In my opinion it is more like telling the coder: "You are going to EXTEND something. Are you sure?" rather than "You are writing 'extends', so whatever you are trying to extend must be designed for extending.". It's silly, why would anyone think something like this? There is the problem with how easy it is to write "extends" that compiles, not with the word itself. This is a problem with Java, not with extending.

Thanks for the paragraph about traits!

czwartek, 15 października 2009

PKreversed_set

Stwierdziłem, że wrzucę przydatny kawałek kodu, który zrobiłem kilka dni temu.
Problem: istnieje implementacja jakiegoś algorytmu w C++ i chcemy, żeby mógł działać też w odwrotnym kierunku, np od końca do początku. Algorytm jest skomplikowany i nie chce się pisać go ponownie. Rozwiązanie problemu metodą Copiego i Pasta + zastąpienie iteratorów iteratorami odwrotnymi nie jest atrakcyjne (bo powstają dwie kopie tego samego kodu do utrzymania itd). Moje rozwiązanie jest eleganckie:
- Zrobić z algorytmu szablon algorytmu, sparametryzowany typem kontenera.
- Zrobić opakowanie na kontener, którego funkcjonalność jest odbiciem lustrzanym funkcjonalności oryginalnego kontenera, np. end() jest zastąpione rend(), typ 'iterator' to tak naprawdę 'reverse_iterator' oryginału itd.
- Wywoływać algorytm raz dla oryginalnego kontenera a raz dla opakowania.

Kod zawiera odbicie lustrzane kontenera std::set.
Można go pobrać tu.

piątek, 9 października 2009

Tower Defense dla wielu graczy.

(już napisałem "w multiplayerze", ale zmieniłem, żeby nie zgrzytać spolszczonymi anglicyzmami)
Mamy taki przedmiot "Advanced Software Developement". Jedynym kryterium oceny końcowej jest projekt programistyczny do zrobienia w parach. Bardzo mi to odpowiada, para jest chyba najlepsza do tworzenia programów, bo całkiem sporo można razem zrobić, jest wzajemna motywacja, a jednocześnie nie rozmywa się odpowiedzialność, a wizja jest bardziej spójna (to taka dygresja). Do rzeczy: podczas naszej burzy mózgów dzisiaj, kiedy to powinno nam przyjść do głowy jak najwięcej pomysłów, a przyszły tylko 3, udało mi się przekonać kumpla do zrobienia gry w Javie, gry typu Tower Defence. Wyróżnianie się gry na tle zawartości zagraconego internetu ma być osiągnięte dzięki temu, że będzie w niej gra wieloosobowa (zmowu zmieniłem z "multiplayer") asymetryczna. Co to znaczy? Z grubsza tyle, że każdy gracz ma zupełnie inne zadania do wykonania: jeden gracz utrzymuje bazę i chroni ją przed atakami, budując ostrzeliwujące wieże, a drugi mówi oddziałom, jaką ścieżkę mają wybrać, wybiera typ żołnieża i rozkazuje mu zniszczyć wyżej wymienioną bazę.
Asymetryczna? Nie przekonałem kolegi do tego pomysłu, nalegał na zrobienie trybu gry, gdzie każdy gracz ma swoją bazę a drugi wysyła na niego odziały. Wymanewrowałem z tej niezręcznej sytuacji argumentując, że najpierw zrobimy tryb asymetryczny, a potem po prostu walniemy na mapę obiekty w trybie asymetrycznym oraz ich odbicie lustrzane, tworząc w zasadzie tryb symetryczny. To stwierdzenie to był jednak tylko sprytny wybieg. Idea jest taka, że jak kolega przekona się do trybu asymetrycznego, zapomni o swoich zmartwieniach. Skąd ta pewność?
Zacząłem dzisiaj analizować fakt, że gry TD są tak popularne; wciągają myślące osoby w wir zadania, a jednocześnie nie męczą. Są to tak naprawdę klasyczne strategie czasu rzeczywistego, ale niepełne, bez tworzenia oddziałów, złożonego dowodzenia i niszczenia nimi bazy wroga.
Przypomniawszy sobie kilka rozgrywek, stwierdziłem że wciąganie w grę polega na pozwoleniu graczowi skupić się na pojedynczym zadaniu, udoskonalać techniki jego wykonywania do granic możliwości. Gry TD to umożliwiają, a wypasione strategie w pudełkach po 100zł już często nie (choć jest to temat na inną dyskusję). Zaletą grania w TD jest więc łatwość, płynność. Dokładniej to analizując, stwierdziłem, że w grze TD można zobaczyć jak zmienienie czegoś w linii obrony wpływa na wynik rozgrywki, to "zobaczenie" jest niczym nieprzerywane, a do tego następstwo przyczynowo skutkowe jest rownoznaczne z bezpośrednim następstwem czasowym. To znaczy, że myśli z łatwością nadążają za tym, co widzimy, a jednocześnie to, co widzimy nadąża za myślami, zmieniając się od razu! Co więcej, dzięki powtarzającym się falom ataków, można z łatwością wyciągać wnioski nie tylko z pojedynczych ataków, ale z całych serii. Dzięki temu dodatkowo unika się losowości wyniku gry, widocznej w komejcyjnych strategiach, gdzie jeden lub dwa ataki decydują tak naprawdę o wyniku.
Ta magia gier TD jest zbyc cenna, żeby można było ją stracić. Niech w grze wieloosobowej jeden gracz gra tak, jak w jednoosobowej, a drugi w zupełnie nowy sposób, też zaprojektowany dla jednej osoby.

Sieci Neuronowe.

Jednak to napiszę. Podjąłem się niedawno nauki sieci neuronowych. Efekt można zobaczyć tu.
Wprowadzenie:
Jest prostokątny samochodzik i trasa, poza którą fizycznie nie da się wyjechać. Samochodzik może kontrolować swoje przyspieszenie oraz skręt kół. Jeżeli uda mu się przejechać przez całą trasę, ma najlepszy wynik, jeżeli nie ruszy się z miejsca lub cofnie, najgorszy. Przejazd zostaje przerwany jeśli samochodzik uderzy w granicę trasy (musi jechać wewnątrz).
Zadanie:
Zrobić program, który potrafi nauczyć samochodzik jechać po (prawie) dowolnej trasie.
Żeby była jasność:
- Nie programuję samochodziku samemu, używając własnej wiedzy o tym jak jeździć. Program ma sam zebrać tę wiedzę.
- Program nie zbiera wiedzy o konkretnej trasie, tylko ogólnie o tym, jak jechać.
Wykonanie:
Ewolucyjne wyznaczenie sieci neuronowej rozwiązującej problem, czyli sterującej samochodzikiem.
Dane wejściowe: bieżąca pozycja, prędkość, to, co "widzi" samochodzik (odległość od granic toru w różnych kierunkach).
Dane wyjściowe: przyspieszenie (4 wyjścia: duże przyspieszenie, mniejsze przysp., duże spowolnienie, mniejsze spowol.), skręt kół (podobne 4 wyjścia).
Algorytm ewolucyjny działa na populacji 50 "mózgów samochodzików". Są wybierane 3 losowe mózgi. Te, które jeszcze nie były sprawdzone w praktyce, są sprawdzanie: kierują przejazdem samochodziku na trasie. Są porównywane ich wyniki. Najgorszy mózg jest wywalany, pozostałe są krzyżowane dyskrenie i tak stworzony mózg jest włączany do populacji.

Filmik przedstawia kilkanaście spośród około pięciu tysięcy przejazdów treningowych wykonych przez program. Pokazane są tylko te, które poprawiają maksymalny wynik, osiągany przez samochodzik.
Program jest napisany w C++ z użyciem biblioteki PKgui.