Java'nın Veri Dünyasına Lambda İle Yolculuğu - II

Lambda ile nasıl daha etkin kod yazarız ?

Improving Code with Lambda Expressions

Burada örnek olarak seçeceğimiz senaryo, en yaygın kullanım örneği olan bir yığın içinde belirli bir kritere uygun öğeleri arayıp bulma senaryosu olacak. Bknz :  "Jump-Starting Lambda". Örnekte bir grup insan içinde değişik kriterlere göre gruplandırma yapılıp, bu gruplardaki kişilere otomatik arama yapılmaktadır. Bizim çalışmamızda ise bağlantıdaki örnekten ufak farklılıklar olmasına rağmen ana fikir korunmuştur.

İnceleyeceğimiz ve üzerinde çalışacağımız örnekte bir grup insan kümesi içinden seçimle üç ayrı gruplandırma yapıp bu gruplara göre hareket tarzı belirleyeceğiz. Şöyle ki :

  • Sürücüler (Drivers): Yaşı 16’dan büyük olanlar
  • Asker adayları (Draftees): Yaşı 18 ile 25 arasında olan erkekler
  • Pilotlar (Pilots)(ticari): Yaşı 23 ile 65 arasında olanlar.

Tabii ki bu programın çalışması için gerekli olan donanım elimizde mevcut olmadığından sadece simüle etmekle yetineceğiz, bunu da konsola mesaj yazarak yapacağız. Bu mesaj içinde de şahısa ait bilgiler olacak. Örneğin, ad, yaş ve gruba özel bilgiler örneğin, e-posta için e-posta adresi, telefon araması için telefon numarası gibi.


Person Class - Şahıs Sınıfı
*Person bir nesne adı olduğundan anlamı belirtilip bundan sonra aslına sadık kalınarak kendisi kullanılacaktır.

Örnek listemizdeki herkes aşağıda deseni verilen Person tipindeki nesne ile tanımlanmaktadır.

9 public class Person {
10   private String givenName;
11   private String surName;
12   private int age;
13   private Gender gender;
14   private String eMail;
15   private String phone;
16   private String address;
17 }  


Buradaki Person sınıfı bir inşacı (Builder) metot kullanarak yeni Person nesneleri üretmektedir. Örnek bir liste oluşturmak için ise aşağıda kodu verilen createShortList metodu kullanılmıştır.

128   public static List<Person> createShortList(){
129 List<Person> people = new ArrayList<>();
130
131 people.add(
132    new Person.Builder()
133          .givenName("Bob")
134          .surName("Baker")
135          .age(21)
136          .gender(Gender.MALE)
137          .email("bob.baker@example.com")
138          .phoneNumber("201-121-4678")
139          .address("44 4th St, Smallville, KS 12333")
140          .build()
141    );
142
143 people.add(
144    new Person.Builder()
145          .givenName("Jane")
146          .surName("Doe")
147          .age(25)
148          .gender(Gender.FEMALE)
149          .email("jane.doe@example.com")
150          .phoneNumber("202-123-4678")
151          .address("33 3rd St, Smallville, KS 12333")
152          .build()
153    );
154
155 people.add(
156    new Person.Builder()
157          .givenName("John")
158          .surName("Doe")
159          .age(25)
160          .gender(Gender.MALE)
161          .email("john.doe@example.com")
162          .phoneNumber("202-123-4678")
163          .address("33 3rd St, Smallville, KS 12333")
164          .build()
165 );
166
 

Birinci Adım

İlk adım olarak Person nesnesi ve arama kriterlerine göre RoboContact adında yeni bir sınıf türetilir ve her bir grup için ayrı bir metot tanımı bu sınıfa eklenir.

RoboContactsMethods.java


1 package com.example.lambda;
  2
  3 import java.util.List;
  4
  5 /**
  6  *
  7  * @author MikeW
  8  */
  9 public class RoboContactMethods {
 10  
 11   public void callDrivers(List<Person> pl){
 12   for(Person p:pl){
 13      if (p.getAge() >= 16){
 14        roboCall(p);
 15     }
 16   }
 17   }
 18  
 19   public void emailDraftees(List<Person> pl){
 20   for(Person p:pl){
 21         if (p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE){
 22      roboEmail(p);
 23         }
 24     }
 25   }
 26  
 27   public void mailPilots(List<Person> pl){
 28   for(Person p:pl){
 29     if (p.getAge() >= 23 && p.getAge() <= 65){
 30        roboMail(p);
 31          }
 32   }
 33   }
 34  
 35  
 36   public void roboCall(Person p){
 37   System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
 38   }
 39  
 40   public void roboEmail(Person p){
 41   System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
 42   }
 43  
 44   public void roboMail(Person p){
 45   System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
 46   }
 47
 48 }

 
Aslında metot isimlerine bakarak hangi gruplar için ne yapılacağı kolayca anlaşılıyor (callDrivers, emailDraftees, mailPilots). Arama kriteri de metotlar içinde gayet açık belirtilmiş ve seçime göre de  ilgili işlem yapılıyor. Gelgelelim bu tasarım bazı olumsuz özelliklere sahip :

  • DRY (Don’t Repeat Yourself). “Kendi kendini tekrar etme !” kaidesine uymuyor.
*DRY bir kavram kısaltması olduğundan anlamı belirtilip bundan sonra aslına sadık kalınarak kendisi kullanılacaktır.
    • Her metot, içinde bir döngü barındırıyor
    • Seçim kriteri her metot içinde yeniden yazılmış
  • Her bir örnek durum için bir sürü yeni metot yazmak gerekiyor
  • Kod esnek değil zira arama kriteri değiştiğinde bir sürü değişiklik ve güncelleme gerektiriyor. Bu da kodun bakım maliyetini artıran bir etken.

Metotları Yenileyelim
Daha iyi bir yapıyı nasıl kurarız ve nereden başlamalıyız? Nereden başlayalım sorusunun cevabı arama kriterleri olacaktır. Eğer ayrıştırmaya yarayan arama kriterleri ayrı metotlara taşınabilirse iyi bir başlangıç olacaktır.

RoboContactsMethods2.java


  1 package com.example.lambda;
  2
  3 import java.util.List;
  4
  5 /**
  6  *
  7  * @author MikeW
  8  */
  9 public class RoboContactMethods2 {
 10   
 
 11   public void callDrivers(List<Person> pl){
 12     for(Person p:pl){
 13       if (isDriver(p)){
 14         roboCall(p);
 15       }
 16     }
 17   }
 18   
 19   public void emailDraftees(List<Person> pl){
 20     for(Person p:pl){
 21       if (isDraftee(p)){
 22         roboEmail(p);
 23       }
 24     }
 25   }
 26   
 27   public void mailPilots(List<Person> pl){
 28     for(Person p:pl){
 29       if (isPilot(p)){
 30         roboMail(p);
 31       }
 32     }
 33   }
 34   
 35   public boolean isDriver(Person p){
 36     return p.getAge() >= 16;
 37   }
 38   
 39   public boolean isDraftee(Person p){
 40     return p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE;
 41   }
 42   
 43   public boolean isPilot(Person p){
 44     return p.getAge() >= 23 && p.getAge() <= 65;
 45   }
 46   
 47   public void roboCall(Person p){
 48     System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
 49   }
 50   
 51   public void roboEmail(Person p){
 52     System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
 53   }
 54   
 55   public void roboMail(Person p){
 56     System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
 57   }
 58
 59 }

Bu adımda kıstaslar kendi metotlarına taşınarak soyutlandırılır. Böylece gruba özel işlem metotlarındaki karar ifadeleri daha basit hale gelir. Yalnız bu durum  program akışında yeni metot çağrımları yapıldığından yeni bir dallanmaya ve de akışta değişikliğe yol açar. Son duruma baktığımızda hâlâ kod tekrarları olduğunu görebiliriz. Yeni metotlar eklemek gerekiyor ki gruplara özel metotları daha basit hale getirebilelim. Acaba arama kriterlerini metotlara geçirmek için daha iyi bir yol bulabilir miyiz ?

Anonim Sınıflar

Konun başında da belirttiğimiz gibi lambda ifadeleri kullanılmaya başlanmadan önce anonim dahili sınıflarla ihtiyaçlarımızı karşılamaya çalışıyorduk. Bu örnek için düşünürsek bir tek test metodu olan ve geriye boolean bir değer dönen MyTest.java arayüzü - ki bu fonksiyonel bir arayüzdür - bu uygulamaya güzel bir örnektir. Arama kriteri, bu metodu çağırırken bir değişken olarak geçirilebilir. Arayüzün kodu şu şekilde görünür :



6 public interface MyTest<T> {
7     public boolean test(T t);
8 }

Şimdi bu yeni arayüz yardımıyla güncellediğimiz  sınıfa bir bakalım.

RoboContactsAnon.java

9 public class RoboContactAnon {
10
11   public void phoneContacts(List<Person> pl, MyTest<Person> aTest){
12     for(Person p:pl){
13       if (aTest.test(p)){
14         roboCall(p);
15       }
16     }
17   }
18
19   public void emailContacts(List<Person> pl, MyTest<Person> aTest){
20     for(Person p:pl){
21       if (aTest.test(p)){
22         roboEmail(p);
23       }
24     }
25   }
26
27   public void mailContacts(List<Person> pl, MyTest<Person> aTest){
28     for(Person p:pl){
29       if (aTest.test(p)){
30         roboMail(p);
31       }
32     }
33   }  
34   
35   public void roboCall(Person p){
36     System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
37   }
38   
39   public void roboEmail(Person p){
40     System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
41   }
42   
43   public void roboMail(Person p){
44     System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
45   }
46   
47 }

Böylece çok önemli bir kazanç sağlamış olduk. Daha önce altı metot kullanırken, şimdi sadece üç adet metotla seçim işlemimizi yapabilir hale geldik. Fakat ciddi bir problemimiz daha var. Bu problemi ancak sınıfımızı test etmek için yazacağımız test sınıfında görebiliriz.

RoboCallTest03.java


  1 package com.example.lambda;
  2
  3 import java.util.List;
  4
  5 /**
  6  * @author MikeW
  7  */
  8 public class RoboCallTest03 {
  9
 10   public static void main(String[] args) {
 11     
 12     List<Person> pl = Person.createShortList();
 13     RoboContactAnon robo = new RoboContactAnon();
 14     
 
 15     System.out.println("\n==== Test 03 ====");
 16     System.out.println("\n=== Calling all Drivers ===");
 17     robo.phoneContacts(pl,
 18         new MyTest<Person>(){
 19           @Override
 20           public boolean test(Person p){
 21             return p.getAge() >=16;
 22           }
 23         }
 24     );
 25     
 26     System.out.println("\n=== Emailing all Draftees ===");
 27     robo.emailContacts(pl,
 28         new MyTest<Person>(){
 29           @Override
 30           public boolean test(Person p){
 31             return p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE;
 32           }
 33         }
 34     );
 35     
 36     
 37     System.out.println("\n=== Mail all Pilots ===");
 38     robo.mailContacts(pl,
 39         new MyTest<Person>(){
 40           @Override
 41           public boolean test(Person p){
 42             return p.getAge() >= 23 && p.getAge() <= 65;
 43           }
 44         }
 45     );
 46     
 47     
 48   }
 49 }


İşte size gerçek hayatta karşılaşacağımız “dikey” problem için harika bir örnek! Kod kolay okunabilir bir kod değil ve hala seçim kıstaslarını her bir durum için tekrar yazma durumundan da kurtulabilmiş değiliz.

Comments

Popular posts from this blog

Automation of daily build process with TlosLite

Java Sürümleri ve Özellikleri Kılavuzu

Java 14'de neler var - 1