问题
Question 1: What is the correct way to build a Use Case (or more than one) with 2 ways to do the same action?
For example:
I have a 3 screens in an iOS app:
1. A map view, which can be "long pressed" and has a camera button.
2. A camera view, which is shown if the user taps the camera button in the map view.
3. A place/pin editing view, which is shown if the user "long presses" the map view, or after the user chooses a photo in the camera view. This editing view has a save button to actually create the place with a photo and the location (long press coordinate or current location in case the camera button was presses).
Title: Create Place
Basic flow:
1. User “long press” on the map.
2. App drops a temporary pin and displays the place editing view.
3. User edits the place information and presses save button.
4. App creates the place and save it.
Title: Create Place
Basic flow:
1. User presses plus button.
2. App displays camera view.
3. User takes a picture.
4. App creates place with current location and picture.
UPDATE based on comments exchanged with bhavik.
Question 2: (Based on bhavik's answer)
So I don't need one presenter for one interactor precisely, I can have 1 interactor and 3 presenters/views.
- In my case, I should have one presenter/view for the map, which is where it starts,
- then I should have one presenter/view for the camera, in case the user taps the camera button
- and one presenter/view for the editing view, in case the user "long presses" or after the user chooses the photo from the camera presenter/view and is redirected to the same editing view.
Is that correct?
Question 3: Should my boundary methods for the interactor always return void?
In bhavik's example they are returning something, but in the VIPER blog and the uncle Bob's video they always return void and the result comes in the form of another boundary method that the interactor calls on the presenter/controller.
Question 4: The VIPER way does not use a controller, only a presenter to talk to the interactor, when the uncle Bob's video uses a controller and a presenter for different interactions with the interactor. Which approach should I take?
Question 5: If my Use Case is something like "Go to other screen", should it even have an interactor? Since the current view will tell its presenter what button was pressed (what view to go to) and this current presenter will tell its wireframe "change to this other wireframe".
回答1:
Question 1: What is the correct way to build a Use Case (or more than one) with 2 ways to do the same action?
In VIPER design, you can create two methods in the same Interactor suitable for each primary and alternates of the use case.
Question 2: (Based on bhavik's answer) So I don't need one presenter for one interactor precisely, I can have 1 interactor and 3 presenters/views.
Based on our discussion and your updates, I think I understand it better.
- Presenter/View should not interact with more than on Interactor.
- Presenter/View may not interact with any Interactor at all as in case of
CameraView
. - They are intermediate views as in Wizards.
- Multiple Presenter/View can interact with single Interactor.
- Interactor does not bind to any Presenter.
- Single Interactor is responsible for single use case and all of its alternate flows. 1-1 relationship.
So, you should have single EditPlacePresenter/View
for EditPlaceInteractor
that pass data Place data with or without Photo.
Question 3: Should my boundary methods for the interactor always return void?
In bhavik's example they are returning something, but in the VIPER blog and the uncle Bob's video they always return void and the result comes in the form of another boundary method that the interactor calls on the presenter/controller.
I think you are referring to the below Presenter method that receives results from the Interactor.
- (void)foundUpcomingItems:(NSArray*)upcomingItems
For the above to work, the Interactor will have delegate instances that will be wired/patched through by the Presenter/Controller looking for result or data. This means the Presenter/Controller is tied to Interactor or their reference or return-function-pointer is passed in each Interactor method call. Is that by design?
I think, Interactor should return data as per the use case. For example Interactor should return the EditPlaceResult with success or failure.
- If success, it should include saved data, for example Place ID.
- If failed, should include fail reason.
This should be part of the use case. If not then, it should not return anything. It will return void and a separate Interactor will be queried by Presenter to check if Map Place was added successfully or not.
References in the blog:
- Presenter contains view logic for preparing content for display as received from the Interactor.
- Its up to the Presenter to take the data returned by the Interactor and format it for presentation.
- The Presenter receives results from an Interactor and converts the results into a form that is efficient to display in a View.
Question 4: The VIPER way does not use a controller, only a presenter to talk to the interactor, when the uncle Bob's video uses a controller and a presenter for different interactions with the interactor. Which approach should I take?
You need to define VIPER routes for following navigation:
- Camera Button: Navigate from
MapView
toCameraView
(Use Location) - Long Press: Navigate from
MapView
toEditPlaceView
(Use Coordinate) - Photo taken: Navigate from
CameraView
toEditPlaceView
- Save Place: Send Interactor a request to Save Place with/without Photo as the case may be and jump back to
MapView
if successful - Back Button: Back to previous View based on Navigation Stack
As per the VIPER blog, view controllers and navigation controllers are used by Presenters and Wireframes.
VIPER Wireframe handles Navigation and makes view controllers become lean, mean, view controlling machines.
Basically Wireframe abstracts away navigation controller and instead provides route definition.
Wireframe
- Owns UINavigationController and UIViewController
- Responsible for creating a View/ViewController and installing it in the window
- Routing are defined in Wireframes and contains navigation logic for describing which screens are shown in which order
Presenter
- Uses Wireframe to perform the navigation
- To keep view controllers lean, VIPER need to give View Controllers a way to inform interested parties when a user takes certain actions - Presenters come here!
- The view controller shouldn't be making decisions based on these actions, but it should pass these events along to something that can - Presenters should be making decisions!
Question 5: If my Use Case is something like "Go to other screen", should it even have an interactor? Since the current view will tell its presenter what button was pressed (what view to go to) and this current presenter will tell its wireframe "change to this other wireframe".
No. Navigation as part of use case may not need Interactor. It is transition only. The target Presenter may need an Interactor. For example, CameraView/Presenter
does not need Interactor but EditPlaceView
needs to save the place.
Overall: The idea behind architectural patterns is to divide a given software application into interconnected parts, so as to separate internal representations of information from the ways that information is presented to or accepted from the user. MVC, MVP, MVVM, VIPER all focus on isolating View, Logic and Navigation in one way or other.
Architectural patterns are limited in what they intend to decompose. We have to understand that architectural patterns do not decompose or isolate everything. Also if one architectural pattern delegates certain responsibilities to certain part, other does not do that at all probably or assign multiple responsibilities to single part.
We are allowed to extend or limit the isolation and decomposition to the extent that it justifies the cause and does not impose unnecessary separation of concerns that overrun the cost. You can choose to use navigation controllers and your presenter can take dependency on them without Wireframe routes defined. These controllers then will be responsible for the navigation between screens.
回答2:
Question: What to do when 2 use cases are related to the same presenter/view? For example, I have a presenter/view that
- Checks if user is logged in and also
- Asks for user location authorization before doing anything else.
Answer
Use Case Modelling
It seems that you are dealing with non-trivial use-case modelling scenarios.
"Check user-login" does not need use case. Similarly, if location authorization is not being updated as part of configuration settings, then it is also not a use-case. They are not good candidates for use-case modelling but rather preconditions or "steps" in other complex use cases. I think they are more like preconditions then "steps" and certainly not individual "use-cases" themselves.
Preconditions should be accompanied by use-case exceptions and non-trivial "steps" hints use-case-reuse via include-dependencies. If preconditions fails, you can provide appropriate messages and options to fulfil those conditions. For complex steps, one use case redirect to other use-case.
The idea is to either opt for exceptions that will terminate the use case with valid message on failed preconditions OR include other use-cases that will ask user to log-in first and resume current use case.
The goal is to break down complex requirements and re-use them. Quoting from Use Case Reuse - Include Dependency. "You use include dependencies whenever one use case needs the behavior of another. Introducing a new use case that encapsulates similar logic that occurs in several use cases is quite common."
In VIPER Way
- Interactors should know about other Interactors if required.
- Rules and preconditions should validated in Interactors or in Entities as required.
- Interactors should return appropriate data for Presenters.
- Presenters should NOT know about other Presenters that are part of other use cases.
- Presenters should send requests to Wireframe.
- Wireframe should take care of appropriate navigation to different Views.
Thus,
CreatePlaceInteractor
should take dependency onUserLogInInteractor
andLocationAuthorizationInteractor
and call them when necessary.- Or
CreatePlaceInteractor
should send back data that Presenter can interpret and ask Wireframe to launchUserLoginPresenter
and/orLocationAuthorizationPresenter
.
This will involve carefully designed interactions and navigation for control flow and data flow using Interactors, Presenters and Wireframes. In the process, you will be challenging lot of core assumptions and hidden unhandled system behaviour.
Since other use cases may also include user-login or authorization-access to finish their task, these use cases should be included but not necessarily executed all the time - conditional function call to UserLogInInteractor and LocationAuthorizationInteractor. If user is logged in and authorization access is already allowed, they will be skipped. When user chooses CameraView, check for user-login and location access. When user chooses EditView directly from MapView, check for for user-login only.
I think you should implement precondition logic into your Interactors and Presenter can use it to make Presentations and Navigations related decisions.
Considering following list of Use Cases:
- "UC001: User Log In".
- "UC002: Get Location Access Authorization".
- "UC003: Show Map".
- "UC004: Create Place on Map".
UC004: Create Place on Map (With Preconditions)
Preconditions:
- User is logged in.
- Location Access has been authorization.
Steps:
- If preconditions are NOT met, run appropriate exceptions.
- Other steps for "Create Map Place"
UC004-E1: User is NOT logged-in or session expired
Steps:
- Show appropriate message to the user for log-in or session expired and provide log-in option (button).
- terminate use case. [user can choose to go back to map view or go to log-in screen]
UC004-E2: Location Access has NOT been authorized.
Steps:
- Show appropriate message to the user for setting location access and provide setting options (button) in the configuration.
- terminate use case. [user can choose to go back to map view or go to settings options]
UC004: Create Place on Map (Without Preconditions)
Steps:
- Check user is logged in and user session is active. If not, run UC001.
- Check if user has previously authorized Location Access. If not, show appropriate message and run UC002.
- Other steps for "Create Map Place"
UC004 > UC001: User is NOT logged-in or session expired
Steps:
- Show appropriate message to the user for log-in or session expired and run UC001
- User logs-in successfully, continue UC004 Step#2
- User is not logged in successfully, terminate the use case.
UC004 > UC002: Location Access has NOT been authorized
Steps:
- Show appropriate message to the user for location authorization and run UC002
- User authorized location success, continue UC004 Step#3
- User did not authorized location access, terminate the use case.
回答3:
Use Case Modeling and VIPER Design considerations for - Create, Edit and View "A Place on Map" for a iOS based mobile application
Your questions
What to do if - Create, Edit and View actions end-up in the same ViewController?
Is it a good idea if MapViewController uses PlacesInteractor to retrieve the places and the CurrentLocationInteractor to request user's location authorization and getting the most updated coordinates?
It is not a problem to combine related logic into single Interactor. But it will not longer be an "Interactor". It will become a "service" or "manager" as in MapPlaceManager/MapPlaceService which will have methods such as:
canCreateMapPlace
createMapPlace(Details)
getMapPlaceCount
getMapPlaceIDs
getMapPlaceDetails(ID)
canUpdateMapPlace
updateMapPlace(ID, NewDetails)
I think the idea was to expose only the intended APIs per use case and hence Interactor - which can clearly state what the user of that Interactor is going to do with it. If it has multiple APIs that can do different things like create/edit/delete map places, then we have to check the method calls in the caller to know what the caller is going to do. Interactors in this sense to me are very high level - business/requirements level interfaces. You can hide your back-end services and managers inside these individual Interactors.
You can take this notion to as far as possible and/or feasible - Feasible rather possible. There will be an extreme where we draw the line, instead of following it too religiously. Business systems tend to be more formal and methodical to give you an example.
In your case, when a button is pressed on your main view that changes your MapPlaceView
into MapPlaceEditView
, you are changing the use case that the new view is going to satisfy. Such in-place view changes are appropriate view design considerations for mobile and also it is use friendly. However, often it encourages complex GUI and messy presenter logic. If it is manageable, cleaner and easier for your ViewController/Presenter to switch "modes" between "Create, View, Edit" - you are good to go. It is not perfect, but it is not wrong. They are Front-End-Participants and have the highest level of freedom and frequency of changes anyway.
An alternate good UI design I have found useful instead of in-place fields editing, is "flipping" the views or any such view transition effect. You can have a MainMapPlacePresenter/ViewController
and it has 3 sub views - for Create, Edit and View. This main view is then responsible for switching between these three views. It enables cleaner navigation, cleaner use case implementations and neat design.
Similarly for CurrentLocationInteractor
, it does two things - 1. request permission to use "device location service" and 2. use "device location service". Now, it seems it is not an Interactor at all. It is Front-End functionality. But you can use SaveAuthorizationInteractor
to save user's choice. But that is different thing. The more I think, Interactors are responsible for things that deal with your system and not with your user.
Presenter does all the "user-talking" and "decision-making" work - they may use device APIs if they need e.g. Location Service. You can create abstract interface ILocationService
and wrapper implementation called LocationService
that will absorb user's device location service - low level implementation and platform-specific detail.
In implementation terms: You can have:
MainPresenter/MainViewController
On Load - Show MapView along with Buttons for Edit and Create Map Place
MapPresenter/MapViewController
On Load - Show Map
Navigations - login, authorization, create, edit
Interactions - none
MapPlaceCreatePresenter/MapPlaceCreateViewController
On Load - call MapPlaceCreateInteractor.canCreateMapPlace - Response = {AllGood, UserNotLoggedIn, LocationIsNotAuthorized}
Interaction - MapPlaceCreateInteractor.createMapPlace - Responses = {PlaceCreatedSuccessfully}
Navigations - Login, Location Authorization, Back to Main View (With Response - UserLoginNeeded, UserAuthorizationForLocationAccessNeeded)
MapPlaceUpdatePresenter/MapPlaceUpdateViewController
On Load - call MapPlaceUpdateInteractor.canUpdateMapPlace
Interaction - MapPlaceUpdateInteractor.updateMapPlace(ExistingMapPlaceID, NewDetails) - Responses = {PlaceUpdatedSuccessfully}
Navigations - Login, Location Authorization, Back to Main View (With Response - UserLoginNeeded, UserAuthorizationForLocationAccessNeeded)
回答4:
It appears that the two use cases seem to have identical end results that is "Create a Place with / without picture" with two ways - "With Camera and Location Service" vs "Manual Data Entry".
The "With Camera and Location Service" use case adds photo as well.
However, I am wondering if two ways of achieving the same result or identical result is considered single use case.
I would design the two as separate use cases if I can, otherwise I would make one as primary or default use case and other approach as an alternative to achieve the same / identical end result.
Use Case: Create Place
Basic flow: Use Camera and Location Service
User presses plus button.
App displays camera view.
User takes a picture.
App creates place with current location "and picture".
Alternate flow A: Use manual data entry
A.1. User “long press” on the map.
A.2. App drops a temporary pin and displays the place editing view.
A.3. User edits the place information and presses save button.
A. 4. App creates the place "without picture" and save it.
Does this make sense?
Updates with specific details
The View Controller responsible to deal with View in iOS, is in fact treated as View in VIPER. See the "View" paragraph. A UIViewController, or one of its subclasses, will implement the View protocol. Hence this controller is your View.
The idea is you need to isolate your Presenter from the knowledge of iOS View or iOS controller. It should deal with iOS specific view and controller via plain data structures like ViewModels. If you can do that, you have successfully isolated Presenter from iOS SDK specific dependencies and you can write and run TDD or Unit tests directly on your Presenters if you want.
However, more interestingly once you succeed in isolating Presenter from View and ViewController, you can isolate Interactor from Presenter easily. Presenter will have to pass data in the form that is acceptable to the Interactor. So Interactor does know nothing about Presenter. It's independent and you can this Interactor (Use case) in command-line, web or desktop-GUI app as easily.
I think there should be one Interactor per use case. If there are alternative flows to the use case, the interactor will have methods with those alternative data structures.
In your case the CreatePlaceInteractor will have two methods:
CreatePlaceWithManualDataEntryResult createPlaceWithManualDataEntry(CreatePlaceWithManualDataEntryRequest)
CreatePlaceWithCameraAndLocationServiceResult createPlaceWithCameraAndLocationService(CreatePlaceWithCameraAndLocationServiceRequest)
There will be three View/Presenters:
CreatePlaceChoicePresenter/View will capture user choice and send the request to the NavigationController or Wireframe as appropriate which will return new Presenter/View according to the user choice.
CreatePlaceWithManualDataEntryPresenter/View will create and convert CreatePlaceWithManualDataEntry ViewModel into CreatePlaceWithManualDataEntry Request and will receive CreatePlaceWithManualDataEntry Result and process accordingly to display the use case result on the view.
CreatePlaceWithCameraAndLocationServicePresenter/View will create and convert CreatePlaceWithCameraAndLocationService ViewModel into CreatePlaceWithCameraAndLocationService Request and will receive createPlaceWithCameraAndLocationService Result and process accordingly to display the use case result on the view.
Apologies for being verbose on request, resonse, viewmodels and method names.
来源:https://stackoverflow.com/questions/26011047/use-case-with-2-ways-for-the-same-action