Skip to content

Commit d35af3b

Browse files
authored
Update 2019-10-21-dry.md
1 parent 9e7aaef commit d35af3b

File tree

1 file changed

+353
-3
lines changed

1 file changed

+353
-3
lines changed

_drafts/2019-10-21-dry.md

Lines changed: 353 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,364 @@ Nie warto na siłę próbować implementować zasadę `DRY` ponieważ może się
2121
## Funkcje
2222
Oczywistym sposobem na zmniejszenie ilości powtórzeń jest umieszczanie bloku kodu w ciele funkcji, która może zostać użyta wielokrotnie w wielu miejscach aplikacji. Należy także wziąć pod uwagę czy istnieje możliwość i zachodzi sens szukania rozwiązania generalnego poprzez funkcję z parametrami wejściowymi.
2323

24+
{% highlight kotlin %}
25+
class RegisterView {
26+
27+
fun register() {
28+
//get inputs from view
29+
val input = "Password123" //mock
30+
val reinput = "Password123"
31+
val email = "example@androidcode.pl"
32+
33+
if(arePasswordsValid(input, reinput) && isEmailValid(email)) {
34+
//make register call
35+
}
36+
else {
37+
//show validation error
38+
}
39+
}
40+
41+
fun arePasswordsValid(password: String, repassword: String): Boolean {
42+
val pattern = Pattern.compile("^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9]).{6,}$")
43+
val matcher = pattern.matcher(password)
44+
return matcher.matches() && password == repassword
45+
46+
}
47+
48+
fun isEmailValid(email: String): Boolean {
49+
val pattern = Pattern.compile("^.+@.+\\..+$")
50+
val matcher = pattern.matcher(email)
51+
return matcher.matches()
52+
}
53+
54+
//some body
55+
}
56+
57+
class ChangePasswordView {
58+
59+
fun changePassword() {
60+
//get inputs from view
61+
val input = "Password123" //mock
62+
val reinput = "Password123"
63+
64+
if(isPasswordValid(input) && input == reinput) {
65+
//make register call
66+
}
67+
else {
68+
//show validation error
69+
}
70+
}
71+
72+
fun isPasswordValid(password: String): Boolean {
73+
val pattern = Pattern.compile("^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9]).{6,}$")
74+
val matcher = pattern.matcher(password)
75+
return matcher.matches()
76+
}
77+
78+
//some body
79+
}
80+
{% endhighlight %}
81+
82+
Walidacja hasła zachodzi w ten sam sposób w conajmniej dwóch miejscach: `RegisterView`, `ChangePasswordView`. Ponadto widok rejestracji sprawdza poprawność wpisanego adresu email. Można przypuszczać, że istnieje spora szansa na ponowne wykorzystanie metod walidacyjnych w innych miejscach aplikacji. W związku z tym warto przenieść metody walidacyjne do jednej klasy pomocniczej `Validator` dodatkowo zachowując ich spójność w całym kodzie.
83+
84+
{% highlight kotlin %}
85+
class ResetPasswordView {
86+
87+
fun register() {
88+
//get inputs from view
89+
val input = "Password123" //mock
90+
val reinput = "Password123"
91+
val email = "example@androidcode.pl"
92+
93+
if(Validator.arePasswordsValid(input, reinput) && Validator.isEmailValid(email)) {
94+
//make register call
95+
}
96+
else {
97+
//show validation error
98+
}
99+
}
100+
101+
//some body
102+
}
103+
104+
class ChangePasswordView {
105+
106+
fun changePassword() {
107+
//get inputs from view
108+
val input = "Password123" //mock
109+
val reinput = "Password123"
110+
111+
if(Validator.arePasswordsValid(input, reinput)) {
112+
//make register call
113+
}
114+
else {
115+
//show validation error
116+
}
117+
}
118+
119+
//some body
120+
}
121+
122+
object Validator {
123+
124+
fun isPasswordValid(password: String): Boolean {
125+
val pattern = Pattern.compile("^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9]).{6,}$")
126+
val matcher = pattern.matcher(password)
127+
return matcher.matches()
128+
}
129+
130+
fun arePasswordsValid(password: String, repassword: String): Boolean {
131+
return isPasswordValid(password) && password == repassword
132+
}
133+
134+
fun isEmailValid(email: String): Boolean {
135+
val pattern = Pattern.compile("^.+@.+\\..+$")
136+
val matcher = pattern.matcher(email)
137+
return matcher.matches()
138+
}
139+
}
140+
{% endhighlight %}
141+
24142
## Stałe
25143
Warto rozważyć zastosowanie zarówno stałych lokalnych jak i globalnych, które poza redukcją powielania zwiększają czytelność kodu w wyniku zastępienia niekoniecznie zrozumiałych nazw zmiennych.
26144

145+
{% highlight kotlin %}
146+
class DateUtils {
147+
148+
fun getDaysInMiliseconds(miliseconds: Long): Long {
149+
return miliseconds / 1000 * 60 * 60 * 24
150+
}
151+
152+
fun getMilisecondsInDays(days: Long): Long {
153+
return days * 1000 * 60 * 60 * 24
154+
}
155+
}
156+
{% endhighlight %}
157+
158+
Metody klasy pomocnicznej `DateUtils` wykorzystują w swoich obliczeniach mnożnik reprezentujący ilość milisekund w dniu. Aby zaoszczędzić powielenia kodu i uniknąć pomyłki należy stworzyć stałą `MILISECONDS_IN_DAYS` i wykorzystać ją w metodach.
159+
160+
{% highlight kotlin %}
161+
class DateUtils {
162+
163+
companion object {
164+
const val MILISECONDS_IN_DAYS: Long = 1000 * 60 * 60 * 24
165+
}
166+
167+
fun getDaysInMiliseconds(miliseconds: Long): Long {
168+
return miliseconds / MILISECONDS_IN_DAYS
169+
}
170+
171+
fun getMilisecondsInDays(days: Long): Long {
172+
return days * MILISECONDS_IN_DAYS
173+
}
174+
}
175+
{% endhighlight %}
176+
27177
## Typy
28178
Wykonanie żądanej operacji może się wiązać z przekazaniem argumentów i zwróceniem wyniku, które mogą się składać z kilku obiektów. W takiej sytuacji zasadnym może być stworzenie nowego typu danych zawierającego obiekty typów argumentów wejściowych lub wyjściowych.
29179

30-
## Polimorfizm
31-
Polimorfizm umożliwia spójne wykorzystanie elementów na kilka różnych sposobów niezależnie od ich typów za pomocą jednego wspólnego interfejsu. Eliminuje instrukcje warunkowe zależne od typu i redukuje powtarzający się kod za pomocą m.in. mechanizmu dziedziczenia.
180+
{% highlight kotlin %}
181+
class PlayerManager {
182+
183+
fun changeNick(id: Long, newNick: String) {
184+
//put newNick to database or send to server
185+
}
186+
187+
fun getLevel(points: Int): String {
188+
return when(points) {
189+
in 0..99 -> "BEGINNER"
190+
in 100..300 -> "JUNIOR"
191+
in 300..599 -> "ADVANCED"
192+
else -> "MASTER"
193+
}
194+
}
195+
196+
fun increasePoints(id: Long, points: Int, percent: Int) {
197+
val newPoints = points + points * percent
198+
updateLevel(id, newPoints)
199+
//put newPoints to database or send to server using id
200+
}
201+
202+
private fun updateLevel(id: Long, points: Int) {
203+
val newLevel = getLevel(points)
204+
//put newLevel to database or send to server using id
205+
}
206+
}
207+
{% endhighlight %}
208+
209+
Metody klasy `PlayerManager` operują na wielu parametrach różnego typu w celu zastosowania operacji na danych zawodnika. Wymienione argumenty są ze sobą logicznie powiązane i mogą składać się na pola klasy `Player`. Wówczas część odpowiedzialności z klasy `PlayerManager` mogłaby zostać przeniesiona do klasy `Player`.
210+
211+
{% highlight kotlin %}
212+
class PlayerManager {
213+
214+
fun changeNick(player: Player, newNick: String) {
215+
player.nick = newNick
216+
//put player to database or send to server
217+
}
218+
219+
fun increasePoints(player: Player, percent: Int) {
220+
player.increasePointsByPercent(percent)
221+
//put player to database or send to server
222+
}
223+
}
224+
225+
data class Player(val id: Long, var nick: String, var points: Int, var level: Level) {
226+
227+
init {
228+
updateLevel()
229+
}
230+
231+
fun increasePointsByPercent(percent: Int) {
232+
points += points * percent
233+
updateLevel()
234+
}
235+
236+
fun updateLevel() {
237+
level = when(points) {
238+
in 0..99 -> Level.BEGINNER
239+
in 100..300 -> Level.JUNIOR
240+
in 300..599 -> Level.ADVANCED
241+
else -> Level.MASTER
242+
}
243+
}
244+
245+
enum class Level(value: String) {
246+
BEGINNER("BEGINNER"),
247+
JUNIOR("JUNIOR"),
248+
ADVANCED("ADVANCED"),
249+
MASTER("MASTER")
250+
}
251+
}
252+
{% endhighlight %}
253+
254+
## Polimorfizm i dziedziczenie
255+
Polimorfizm umożliwia spójne wykorzystanie elementów na kilka różnych sposobów niezależnie od ich typów za pomocą jednego wspólnego interfejsu. Eliminuje instrukcje warunkowe zależne od typu i redukuje powtarzający się kod. Mechanizm dziedziczenia pozwala na wykorzystanie współdzielonego kodu typu bazowego przez jego podtypy.
256+
257+
{% highlight kotlin %}
258+
class Book(val index: Long, val title: String, val description: String, val year: Int,
259+
val author: String, val topics: List<String>) {
260+
261+
fun getSummary(): String {
262+
return "$title ($year) \n $description"
263+
}
264+
265+
fun print() {
266+
//get Book from database by index and print to file
267+
}
268+
}
269+
270+
class Article(val index: Long, val title: String, val description: String, val year: Int,
271+
val author: String, val pages: Int) {
272+
273+
fun getSummary(): String {
274+
return "$title ($year) \n $description"
275+
}
276+
277+
fun print() {
278+
//get Article from database by index and print to file
279+
}
280+
}
281+
282+
class Film(val index: Long, val title: String, val description: String, val year: Int,
283+
val director: String, val actors: List<Actor>, val duration: Int) {
284+
285+
fun getSummary(): String {
286+
return "$title ($year) \n $description"
287+
}
288+
}
289+
290+
class Music(val index: Long, val title: String, val description: String, val year: Int,
291+
val band: String, val duration: Int) {
292+
293+
fun getSummary(): String {
294+
return "$title ($year) \n $description"
295+
}
296+
}
297+
{% endhighlight %}
298+
299+
Klasy `Book`, `Article`, `Film`, `Music` posiadają właściwości i cechy wspólne, które powinny zostać wyabstrachowane do typu wspólnego `Item`. Ponadto klient wykorzystujący te obiekty jest zmuszony do sprawdzania typów i rzutowania, aby dokonać wydruku elementów papierowych. Zachowanie opisujące możliwość wydruku może zostać przeniesione do interfejsu `Printable` i jego implementacji w wybranych klasach.
300+
301+
{% highlight kotlin %}
302+
class Book(index: Long, title: String, description: String, year: Int,
303+
val author: String, val topics: List<String>) : Item(index, title, description, year), Printable {
304+
305+
override fun print() {
306+
//get Book from database by index and print to file
307+
}
308+
}
309+
310+
class Article(index: Long, title: String, description: String, year: Int,
311+
val author: String, val pages: Int) : Item(index, title, description, year), Printable {
312+
313+
override fun print() {
314+
//get Article from database by index and print to file
315+
}
316+
}
317+
318+
abstract class Item (val index: Long, val title: String, val description: String, val year: Int) {
319+
320+
fun getSummary(): String {
321+
return "$title ($year) \n $description"
322+
}
323+
}
324+
325+
class Film(index: Long, title: String, description: String, year: Int,
326+
val director: String, val actors: List<Actor>, val duration: Int) : Item(index, title, description, year)
327+
328+
class Music(index: Long, title: String, description: String, year: Int,
329+
val band: String, val duration: Int) : Item(index, title, description, year)
330+
331+
interface Printable {
332+
333+
fun print()
334+
}
335+
{% endhighlight %}
32336

33337
## Moduły
34-
Dzielenie kodu na moduły umożliwia nie tylko wyróżnienie części logicznych i funkcjonalnych aplikacji, ale może ułatwić ponowne użycie danej funkcjonalności w innym projekcie. W szczególności są to zewnętrzne biblioteki i wtyczki.
338+
Dzielenie kodu na moduły umożliwia nie tylko wyróżnienie części logicznych i funkcjonalnych aplikacji, ale może ułatwić ponowne użycie danej funkcjonalności w innym projekcie. W szczególności są to zewnętrzne biblioteki i wtyczki.
339+
340+
{% highlight kotlin %}
341+
class Calculator {
342+
343+
fun sqrt(value: Double): Double {
344+
return value * value
345+
}
346+
347+
fun doubleSqrt(value: Double): Double {
348+
return 2 * sqrt(value)
349+
}
350+
351+
fun pow(value: Double, exponent: Int): Double {
352+
return if(exponent == 0)
353+
1.0
354+
else {
355+
var result = value
356+
for(i in exponent downTo 1) {
357+
result *= value
358+
}
359+
result
360+
}
361+
}
362+
363+
fun doublePow(value: Double, exponent: Int): Double {
364+
return 2 * pow(value, exponent)
365+
}
366+
}
367+
{% endhighlight %}
368+
369+
Klasa `Calculator` dostarcza zbiór metod wykonujących obliczenia matematyczne. Część definicji zawiera się jednak w pakiecie matematycznym `Math` w związku z czym mogą one zostać wykorzystane zamiast autorskiej implementacji.
370+
371+
{% highlight kotlin %}
372+
class Calculator {
373+
374+
//use Math module instead of own functions
375+
376+
fun doubleSqrt(value: Double): Double {
377+
return 2 * Math.sqrt(value)
378+
}
379+
380+
fun doublePow(value: Double, exponent: Int): Double {
381+
return 2 * Math.pow(value, exponent.toDouble())
382+
}
383+
}
384+
{% endhighlight %}

0 commit comments

Comments
 (0)