Задать вопрос
  • Android development: каким должен быть масштабируемый код?

    @betterxyz
    В любой профессиональной книге по Android программированию.
    Например https://www.labirint.ru/books/596118/

    Легко масштабируемый продукт — это продукт, на внесение изменений в который уходит минимум средств (под средствами понимаются любые трудозатраты: будь то труд/ часы разработчика или тестировщика, время, затрачиваемое на управление проектами и даже время на коммуникацию!)


    Информация
    Ответ написан
    Комментировать
  • Android development: каким должен быть масштабируемый код?

    @orbit070
    Плохо масштабируемый - это когда у вас в одном классе и девки пляшут и коровы пасутся. Погуглите 'архитектура андроид приложений', mvvm, mvp. Если в двух словах, то разделение приложения на 'слои' и позволяет нормально масштабировать и поддерживать приложение, примеры такого разделения как раз найдете загуглив вышесказанное
    Ответ написан
    Комментировать
  • Android development: каким должен быть масштабируемый код?

    @terminator-light
    Для того чтобы писать масштабируемый необходимо знание не только паттернов GOF,
    но и архитектурных: MVC, MVP, MVVM, MVI. Важно изучить достоинства и недостатки каждого.
    Во главу угла также стоят принципы SOLID, далее приведу небольшие примеры:
    1. Single Responsibility principle - принцип единственной ответственности. Метод/класс должен выполнять только одну задачу. Например, метод, предназначенный для загрузки данных из сети, не должен заниматься обработкой ошибок.
    Псевдокодом напишу:
    spoiler
    public void loadData(String url){
    	repository.fetchProducts().get(products->{
    		view.showProducts(products);
    	}, throwable ->{
    		if(throwable instanceof IOexception){
    			view.showNoNetwork();
    		}else(throwable instanceof HTTPException){
    			HTTPException exception = (HTTPException)throwable;
    		 	switch(exception.getCode()){
    		 		case 400:
    		 			view.showError(exception.getMessage());
    		 			break;
    		 		case 401:
    		 			view.showUnauthorized();
    		 			break;
    		 			...
    		 	}
    		}
    		...
    	});
    }

    Вместо этого вторую часть нужно выделить в другой метод/класс.
    spoiler
    public void loadData(String url){
    	repository.fetchProducts().get(products-> view.showProducts(products), 
    		throwable -> ErrorUtil.handleError(throwable, view));
    }
    
    public class ErrorUtil{
    	public static void handleError(Throwable throwable, View view){
    		if(throwable instanceof IOexception){
    			view.showNoNetwork();
    		}else(throwable instanceof HTTPException){
    			HTTPException exception = (HTTPException)throwable;
    		 	switch(exception.getCode()){
    		 		case 400:
    		 			view.showError(exception.getMessage());
    		 			break;
    		 		case 401:
    		 			view.showUnauthorized();
    		 			break;
    		 			...
    		 	}
    		}
    		...
    	}
    }


    2. Open/Closed principle - принцип открытости/закрытости. Код должен быть открыт для добавления функциональности, но закрыт для изменения.
    Например, есть такой код для работы с тулбаром. Если экранов будет много с разными тулбарами,
    то постоянно придется добавлять новую ветку case, а значит изменять класс ToolbarManager,
    при этом есть возможность появления ошибки в местах, касающихся и других case-веток
    spoiler
    public class ToolbarManager{
    	public void showToolbar(int type){
    		switch(type){
    			case MAIN:
    				....
    				//огромный кусок кода для показа тулбара для главного экрана
    				....
    				break;
    			case PROFILE:
    				...
    				//огромный кусок кода для показа тулбара для экрана профиля
    				...
    			...
    		}
    	}
    }

    Решение: воспользоваться одним из принципов ООП - полиморфизмом. Теперь, если понадобится добавить
    новый экран, нужно будет просто реализовать интерфейс, и это не будет касаться кода других экранов.
    spoiler
    public interface ToolbarManager{
    	void showToolbar();
    }
    
    public class MainToolbarManager implements ToolbarManager{
    	public void showToolbar(){
    		....
    		//огромный кусок кода для показа тулбара для главного экрана
    		....
    	}
    }
    
    public class ProfileToolbarManager implements ToolbarManager{
    	public void showToolbar(){
    		....
    		//огромный кусок кода для показа тулбара для экрана профиля
    		....
    	}
    }

    3. Liskov Substitution principle - принцип подстановки Барбары Лисков гласит: Если класс B - это подтип A, то мы должны иметь
    возможность заменить A на B, не нарушая поведение программы.
    Для данного принципа не могу придумать пример, связанный с Android, но мне понравился этот пример,
    взятый из этого сайта https://www.baeldung.com/solid-principles
    spoiler
    public interface Car {
        void turnOnEngine(); //запустить двигатель
        void accelerate(); //подать газ
    }
    
    public class MotorCar implements Car {
     
        private Engine engine;
     
        public void turnOnEngine() {
            //вруби мотор!
            engine.on();
        }
     
        public void accelerate() {
            //поезжай вперед!
            engine.powerOn(1000);
        }
    }

    Наш класс удовлетворяет интерфейсу, у нас есть машина, которая имеет свой мотор,
    и мы можем ускориться. Но мы живем в 2019 году, а Илон Маск старательный человек. Мы живем в эпоху электрокаров:
    spoiler
    public class ElectricCar implements Car {
     
        public void turnOnEngine() {
            throw new AssertionError("А у меня вообще нет двигателя");
        }
     
        public void accelerate() {
            //ускорение сумасшедшее!
        }
    }

    Выбрасывая машину без двигателя в общую кучу, мы меняем поведение нашей программы.
    Это грубое нарушение принципа подстановки Барбары Лисков. Решить данную проблему будет непросто.
    Но одним из решений было бы разбиение нашей модели на мелкие интерфейсы, которые учитывают состояние без двигателя
    spoiler
    public interface Engineful {
    
        void turnOnEngine();
    }
    public interface Acceleratable {
        void accelerate();
    }
    public class MotorCar implements Engineful, Acceleratable {
     
        private Engine engine;
     
        public void turnOnEngine() {
            //вруби мотор!
            engine.on();
        }
     
        public void accelerate() {
            //поезжай вперед!
            engine.powerOn(1000);
        }
    }
    public class ElectricCar implements Acceleratable {
        public void accelerate() {
            //ускорение сумасшедшее!
        }
    }


    4. Interface segregation principle - прин­цип раз­де­ле­ния интер­фейса.
    Создавайте гранённые мелкие интерфейсы. Клиентский код не должен зависеть от функций интерфейса, которые он
    не будет использовать. Возьмем пример из Android SDK, убрал некоторые детали для простоты:
    spoiler
    public interface TextWatcher{
        public void beforeTextChanged(CharSequence s, int start, int count, int after);
        public void onTextChanged(CharSequence s, int start, int before, int count);
        public void afterTextChanged(Editable s);
    }


    И клиентский код:
    spoiler
    editText.addTextChangedListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    
        }
    
        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
    
        }
    
        @Override
        public void afterTextChanged(Editable s) {
        	//метод, который нам нужен
        	//какие-то полезные действия
        }
    });

    Как видно остальные методы нам не нужны, и мы их не используем. Здесь явное нарушение ISP,
    т.к. интерфейс навязывает использование других методов

    5. Dependency inversion principle - принцип инверсии зависимостей.
    Высокоуровневые модули не должны зависеть от низкоуровневых.
    Абстракции не должны зависеть от деталей. Но детали зависят от абстракций.
    Пример: у нас есть класс Repository, отвечающий за получение данных из разных источников.
    Проблема в том, что объекты жестко заданы в конструкторе. И мы не имеем возможности
    поменять реализацию AppDataBase на FakeDataBase для тестов.
    spoiler
    public class Repository{
    	private final NetworkManager networkManager;
    	private final AppDataBase appDataBase;
    	public Repository(){
    		this.networkManager = new NetworkManager();
    		this.AppDataBase = new AppDataBase();
    	}
    }


    Поэтому нам следует как-то развязать жесткую связь, выделив интерфейсы.
    spoiler
    public interface RemoteDataSource{}
    public interface LocalDataSource{}
    public class NetworkManager implements RemoteDataSource{}
    public class AppDataBase implements LocalDataSource{}
    
    public class Repository{
    	private final RemoteDataSource remoteDataSource;
    	private final LocalDataSource localDataSource;
    	public Repository(RemoteDataSource remoteDataSource, LocalDataSource localDataSource){
    		this.remoteDataSource = remoteDataSource;
    		this.localDataSource = localDataSource;
    	}
    }


    А теперь мы можем тестовую реализацию источников:
    public class FakeNetworkManager implements RemoteDataSource{}
    public class FakeAppDataBase implements LocalDataSource{}

    и вызов:
    Repository repository = new Repository(new FakeNetworkManager(), new FakeAppDataBase());


    У Мартина Фаулера есть хорошая книга Рефакторинг: Улучшение существующего кода и Р.Мартина Чистый код
    Ответ написан
    2 комментария
  • Почему в очереди не используется нулевой индекс в массиве?

    lxsmkv
    @lxsmkv
    Test automation engineer
    если очередь пуста указатель getLoc имеет значение 0. Т.е. указывает на нулевой элемент. При добавлении элементов указатель смещается каждый раз на единицу вперед.
    Если бы мы начинали класть элементы начиная с нулевого элемента, то пришлось бы что нибудь придумывать чтобы обозначить пустую очередь. Можно было бы поставить getLoc на -1.
    В данной реализации, если очередь пуста, возвращаемое значение при запросе будет 0. Это как бы специальное значение-маркер. Однако есть побочный эффект от такой реализации. Если функция get будет использоваться где-то, то нужно будет всегда помнить о значении 0. Иначе можно будет подумать, что в очереди содержится элемент 0.

    Более явной на мой взгляд была бы реализация с использованием boolean isEmpty и выбрасыванием исключения при попытке получить последний элемент в пустой очереди.
    Ответ написан
    Комментировать