1

I am trying to learn Clean Architecture in Android Development. I read this article : RecyclerView multi type without casting

Everything is fine, but in my project, I need to get the response from an API and then I need to create list of multiple-types objects

This is my current code :

val type = api.response.member.type
if (type == "secretary") {
    createSecretaryDepartmentInstance()
} else if (type == "head") {
    createHeadDepartmentInstance()
} else if (type == "security") {
    createSecurityInstance()
} else if (type == "teacher") {
    createTeacherInstance()
} else if (...) {
    ...
} 
and so on ...
...
..
.

Is my code violate OpenClosedPrinciple? If no, why? If yes, then how to fix it?

zihadrizkyef
  • 1,849
  • 3
  • 23
  • 46
  • The question is whether the code needs to be open-closed at all. Are you exposing it as an API to some third developers and client code? – iTollu Jul 28 '20 at 15:28
  • This code is not open-closed, as you need to modify the code to handle more cases. But it doesn't mean that you should introduce a plugin system to support this principle. This code is readable: you see all the logic in the single place. You can add a catch-all case for robustness. You know, which cases are supported and which are not, just by looking at this code. – iTollu Jul 28 '20 at 15:32
  • @iTollu i dont understand your first comment – zihadrizkyef Jul 29 '20 at 07:48
  • @iTollu thank you for pointing me that this is not open-closed. Can you tell me how to make it open-closed? – zihadrizkyef Jul 29 '20 at 07:49

3 Answers3

2

As far as I understand, your code is handling a response from some API, which can be any of "secretary", "head", "security", "teacher" and anyone who will be added in the future. It is acting as a factory that creates an object of specific type based on the data in the response.

What it means to be open-closed? It means, that you can support additional types of responses without updating and rebuilding this piece of code. Actually, the "closed" part of the principle says that the code should actively discourage changes as a means of extension.

The part that prevents extension without modification is the knowledge of the specific types. If you want it open-closed - get rid of this knowledge. You can change it for knowledge about some abstraction that all specific types do satisfy.

For every specific type you can provide a factory with two methods: doesResponseMatches(response) and createInstance(response). Then the responsibility to construct a specific instance from a response will be in hands of this specific type itself.

Next, you code can keep a collection of such individual instance factories. Upon each response it will iterate over this collection and find the one that matches.

How do you get such collection?

In your application there should be some central piece of code that (usually during startup) wires all pieces together. This piece of code knows about all dependencies of the application, about specific implementations and configuration and about the startup sequence. So, it depends both on the abstract piece of code and on specific response types. During the startup it can register all specific type factories with the open-closed code. To fill that collection inside it.

Another way to register those specific "extensions" is to give them the instance of open-closed code and let them register themselves with some method.

So, there are three parties here. The "open-closed" module, the application wiring module and an extension module. You may pick who whether extension modules know about the "open-closed" one, or they don't - and then the wiring module does a little bit more.

But back to my first comment. I question whether you actually need to adhere to the open-closed principle in this code. If you maintain and distribute both all the code in a single artifact and don't expose this "open-closed" part to some other developers for use... I.e. if it is just you internal implementation detail - than your original example is actually simpler and more readable. Because from a single screen you can see right away to which parts of code the control will go next. It is easier to debug and reason about.

iTollu
  • 999
  • 13
  • 20
2

What you want to achieve if some common pattern in android named DelegateAdapter: (https://android.jlelse.eu/keddit-part-4-recyclerview-delegate-adapters-data-classes-with-kotlin-9248f44327f7

For this you will need to create an interface:

 interface ViewType {
     fun getViewType: Int
 }

Then your members should extend of this interface:

 class SecretaryViewType : ViewType {

     override fun getViewType = SECRETARY_VIEW_TYPE
 }

Now you can use a common design pattern, the factory to create instances of the ViewType according to the memberId:

 class MemberViewTypeFactory {
      fun create(memberId: String) : ViewType {
        return when (type) {
           "secretary"-> createSecretaryDepartmentInstance()
           "head"-> createHeadDepartmentInstance()
           "security"-> createSecurityInstance()
           "teacher"-> createTeacherInstance()
           else -> { defaultMethod() }
        }
    }
 }

Now you can create a list of ViewTypes:

 val factory = MemberViewTypeFactory()
 val memberViewTypeList = mutableListOf<ViewType>()
 // You will have the code here to get a list of id's 
 val listOfMemberIds : List<String> = ......
 // Now you can iterate with the list an convert the ids to ViewTypes
 memberViewTypeList = listOfMembers.map { factory.create(it}
Gerardo Suarez
  • 352
  • 2
  • 13
-1

Its not affected OpenClosedPrinciple. Pretty optimize can we make above code as

when (type) {
    "secretary"->createSecretaryDepartmentInstance()
    "head"->createHeadDepartmentInstance()
    "security"-> createSecurityInstance()
    "teacher"-> createTeacherInstance()
    else -> { defaultMethod() }
}
iknow
  • 8,358
  • 12
  • 41
  • 68
sasikumar
  • 12,540
  • 3
  • 28
  • 48