Don't get me wrong, I don't want to minimise the importance of object's attributes. The behavior of the object often depends on them, e.g. if the age is one of person’s attributes, it's clear that it determines many activities that the person can or cannot do.
what is a method ?
Methods determine what instances of specific class can do, how they can interact with other objects.When you are designing your application, this is where you should start — with defining a method. With specifying the things objects should do. When you have the “what” part done, you will have to solve the "how" part anyway. And during this part, you will often discover attributes that will make it possible for objects to do what you design them to do. Starting from this side (behavior) helps us minimise the possibility of adding irrelevant structure's details.
this fancy method...
We can either declare or define a method.We can declare it only in interfaces or abstract classes. A declaration is a guarantee that a particular method will be implemented by the class that will implement an interface or extend an abstract.
A declaration consists of a method name, the list of its parameters and a specified result type.
When we add the body (provide specific implementation) to the method’s declaration, we can talk about this method’s definition.
It's ok to have a many methods with the same name in the same class/interface as long as they differ with the parameters list (either type of amount). However, we cannot create many methods with the same name and parameters, but with different type of the returned value. Why? Because it would not always be possible to determine which method we would like to invoke. A compiler would have to first look at the type of the variable that will hold the result and then decide. And what would happen if we skipped the returned value? When we just wouldn't care? It would be impossible to specify which method should be invoked.
That's why only the name and list of the parameters identify methods.
all the methods we have...
We know how to declare a method. We know how to define it. We know what is needed to do so.It's also good to know what kind of method we have:
- methods that define functionality determine object's behavior. These methods support interaction between objects.
- getters/setters allow us to read and modify values of the object's attributes. They are often abused so I encourage you to think twice before you decide to implement them. Why? It is their behavior that interests us, not implementation details.
And what about encapsulation? When we can read and write object’s attribute it's no longer hidden from us.
I'm not saying that you shouldn't use those methods at all. But it's really important that when you are doing so you will be certain that this is the solution that you really need. Accessors aren't bad. Their invalid usage is. - the special ones are methods that are somehow built-in object. Mostly because of the language that we are using. They’re used in specific situations or in particular life cycles of an object. In Java those methods are ie. constructors, finalize(), toString().
- the ones that are abstract - to be clear, those aren't methods, but declarations. We are using them in interfaces and abstract classes to guarantee that a particular method will exist in child classes.
- the ones that are static - methods the invoking of which doesn't require object existence. Class is enough.
The presence of both static and non-static method in one class usually (always?) means violation of Single Responsibility Principle.
and how will code look like?
As you remember we are building an application for transporting company.Today we will focus on a few more things that are needed:
- Adding a new contractor with specified Employer Identification Number (EIN), unique name and address,
- Creating invoices and sending them via email to our contractor,
- Transferring money to account of contractor that services the company used,
- Supporting contractor’s bank accounts for various currencies,
- Managing contact persons from the contractor’s side.
Wait no longer! Let's implement it!
We know that each contractor needs to have a specified EIN, unique name and office address. That means that there's no point adding a contractor (create an instance of Contractor class) without those attributes set. In that case it seems reasonable to pass them in the constructor:public class Contractor { public Contractor(EmployerIdentifcationNumber ein, Name name, Address address) { this.ein = ein; this.name = name; this.address = address; } }
Another functionality that we need to implement is the possibility of creating invoices. We will focus only on gathering data required to issue an invoice:
- contractor’s name
- full address
- contractor’s EIN
It can result with the following code:
public class Contractor { public ContractorDTO getDataToInvoice() { return new ContractorDTO(ein, name, address); } } public class ContractorDTO { public final EmployerIdentifcationNumber ein; public final Name name; public final Address address; public ContractorDTO(EmployerIdentifcationNumber ein, Name name, Address address) { this.name = name; this.ein = ein; this.address = address; } }
We can simply assume that all parameters of Address and ContractorDTO constructors are Value Objects so we don't have to create a copy to make sure they won't change through reference in DTOs.
what about sending emails?
We can get information about our contractor. So far, so good.But we still will need to send out the issued invoice somewhere. We won’t do it without an email address.
We can agree that we will use the one assigned to contractor's contact persons:
class ContactPerson { public Email getEmail() { return email; } }
But we still need to take this address (or addresses because there can be many contact persons) from our contractor:
public class Contractor { public List<Email> getEmails() { return contactPersons.stream() .map(ContactPerson::getEmail) .collect(Collectors.toList()); } }
maybe some bank account number?
The last thing that we have to implement (at least in this iteration) is the functionality that will allow us to transfer money. To make it possible we first need to get the Contractor's bank account. The only difficulty is that this has to be account that supports the chosen currency.Let's start with method in BankAccount class. First, we need to check whether a specific account supports a given currency:
class BankAccount { public boolean isSupportCurrency(Currency currency) { return this.currency.equals(currency); } }
Next we need to somehow get this number:
class BankAccount { public AccountNumber getAccountNumber() { return number; } }
And finally, we need to have a possibility to get this account number from Contractor:
public class Contractor { public AccountNumber getBankAccountNumberForCurrency(Currency currency) throws UnsupportedCurrencyException { Optional<BankAccount> bankAccountForCurrency = aBankAccountForCurrency(currency); if (bankAccountForCurrency.isPresent()) { return bankAccountForCurrency.get().getAccountNumber(); } throw new UnsupportedCurrencyException("There is no bank account number for given currency: " + currency.getCurrencyCode()); } private Optional<BankAccount> aBankAccountForCurrency(Currency currency) { return bankAccounts.stream() .filter((bankAccount) -> bankAccount.isSupportCurrency(currency)) .findFirst(); } }
Implementation of getBankAccountNumberForCurrency() is not perfect. For simplicity I decided to throw an exception in case of lack of back account, but maybe returning null or Null Object would be more appropriate? As usual, everything depends.
let’s modify something
Ok, it was not over. There are still a few things that need to be done.Let’s read requirements once again. We see that have to be possibility to change contractor’s address. That can result in the code similar to the following:
public class Contractor { public void changeAddress(Address address) { this.address = address; } }
We also need to somehow manage contractor’s bank accounts. For a matter of simplicity, we assume that it is possible to have only one account for a specific currency.
It can lead us to the following code:
public class Contractor { public void addBankAccount(BankAccount bankAccount) throws CurrencyAlreadySupportedException { Optional<BankAccount> bankAccountForCurrency = bankAccounts.stream() .filter((presentBankAccount) -> presentBankAccount.supportTheSameCurrencyAs(bankAccount)) .findAny(); if (bankAccountForCurrency.isPresent()) { throw new CurrencyAlreadySupportedException("There is a bank account that supports the same currency."); } bankAccounts.add(bankAccount); } public void removeBankAccountForCurrency(Currency currency) { Optional<BankAccount> bankAccountForCurrency = aBankAccountForCurrency(currency); if (bankAccountForCurrency.isPresent()){ bankAccounts.remove(bankAccountForCurrency.get()); } } } class BankAccount { public boolean supportTheSameCurrencyAs(BankAccount bankAccount) { return isSupportCurrency(bankAccount.currency); } }
It would also be good to add method that will check whether the contractor already has a bank account for particular currency, but I’m leaving the implementation of it to you :)
and what about contact persons?
Our last requirement is about allowing contractor to have more than one contact person. But I will allow you to write it on your own.Good luck.
that’s all folks
Today we learned something about object’s method. However, I need to warn you that’s only a starting point and there’s a lot more to learn :)Next posts in series soon.
Enjoy your code.
Make Objects, not Spaghetti :)
In this series I also wrote about:
No comments:
Post a Comment