Внедрение зависимостей

Я пришел, чтобы задать вопрос о шаблоне проектирования под названием Dependency Injection.

Идентификатор позволяет объекту знать свои зависимости через интерфейс, а не по его реализации. Таким образом, реализация может варьироваться без ведома зависимого объекта. Большим преимуществом ID является слабая связь между объектами.

Я понимаю, как это работает и для чего это нужно, но это меня совсем не закрывает. Идентификатор имеет окно, как говорится в цитате, для разделения между классами ... но у него есть большой недостаток: когда я передаю объект своему объекту, а не объекту, я теряю функциональность, то есть у меня будет только методы интерфейса, но не объекты.

Итак, стоит ли объекту не иметь никаких зависимостей, теряя при этом функциональность? Я знаю, что это не так, как я говорю, конечно, есть кое-что, в чем я ошибаюсь, и в этом нет упомянутого недостатка. Но, насколько я понимаю, я так понимаю.

3
задан 05.04.2016, 01:57
3 ответа

Сначала нужно понимать, что интерфейс сходный по контракту, потому что он устанавливает функциональность, которую он должен предлагать те, кто осуществляют ее.

Это поставляет преимущество, которого в клиента интерфейса "он не является значимым" 1 внутреннее поведение осуществления, в действительности то, что он импортирует, состоит в том, чтобы он выполнил функциональность, установленную интерфейсом. Хотя это верно уже не возможно соглашаться на какие-то методы, свойственные классу, но то, что Я ДАЛ индийская, состоит в том, что эти методы не являются значимыми для функциональности, которая выполняет интерфейс.

───────
1. В клиента интерфейса не является значимым внутреннее поведение, но в программистов / дизайнеров да он мы значимый. Это происходит главным образом, когда требуют, чтобы изменить осуществление для каких-то случаев, в особенности как например применять оптимизацию или другие главные файлы рисунка.


Я помещаю тебе две сцены, где возможно видеть важность DI, и где методы, свойственные классу не являются совсем значимыми.

Сцена 1: Я имею доступ в источник данных прямо. Основной пример главного файла Дао.

//Entidad a utilizar con estructura similar a la tabla en BD
public class Entidad {
    private int id;
    private String nombre;
    //getters y setters...
}
//De esta interfaz, solo importa que guarde el dato y permita
//obtener dicho dato por su id
public interface EntidadDao {
    void save(Entidad entidad);
    Entidad find(int id);
    List<Entidad> findAll();
}
//implementación básica que utiliza conexión a base de datos
//directamente
public class EntidadDaoBaseDatosImpl implements EntidadDao {
    private Connection obtenerConexion() {
        /* obtiene una conexión a base de datos y la devuelve */
    }

    private int getIdGenerado(PreparedStatement pstmt) throws SQLException {
        /* método que utiliza Statement#getGeneratedKeys
           y devuelve el id generado en esta sentencia INSERT */
    }

    @Override
    public void save(Entidad entidad) {
        String sql = "INSERT INTO entidad (nombre) VALUES (?)";
        try (Connection con = obtenerConexion();
            PreparedStatement pstmt = con.prepareStatement(sql);) {
            pstmt.setString(1, entidad.nombre);
            pstmt.executeUpdate();
            entidad.setId( getIdGenerado(pstmt) );
        } catch (SQLException e) {
            logger.error("Error al guardar Entidad en bd.", e);
        }
    }

    private Entidad creaEntidad(ResultSet rs) {
        Entidad entidad = null;
        if (rs.next()) {
            entidad = new Entidad();
            entidad.setId(rs.getInt("id"));
            entidad.setNombre(rs.getInt("nombre"));
        }
        return entidad;
    }

    @Override
    public Entidad find(int id) {
        Entidad entidad = null;
        String sql = "SELECT id, nombre FROM Entidad WHERE id = ?";
        try (Connection con = obtenerConexion();
            PreparedStatement pstmt = con.prepareStatement(sql);) {
            pstmt.setInt(1, id);
            try (ResultSet rs = pstmt.executeQuery()) {
                entidad = creaEntidad(rs);
            }
        } catch (SQLException e) {
            logger.error("Error al guardar Entidad en bd.", e);
        }
        return entidad;
    }

    @Override
    public List<Entidad> findAll() {
        String sql = "SELECT id, nombre FROM Entidad";
        List<Entidad> listaEntidades = new ArrayList<>();
        /* implementación para conectarse a base de datos, ejecutar el query y devolver todos los resultados como una lista */
        return listaEntidades;
    }
}
//Cliente de la interfaz Dao
public class EntidadController {
    //se declara un parámetro del tipo interfaz, no de la clase
    private EntidadDao entidadDao;

    //se habilita la inyección de dependencia por constructor
    public EntidadController(EntidadDao entidadDao) {
        this.entidadDao = entidadDao;
    }

    public void save(Entidad entidad) {
        //se puede validar que el objeto entidad sea válido
        if (esEntidadValida(entidad)) {
            //como puedes ver, de este objeto lo único que importa
            //es que pueda grabar la entidad
            //sus métodos obtenerConexion y getIdGenerado son irrelevantes
            //para el uso de EntidadController, no los necesita
            entidadDao.save(entidad);
        }
    }

    private boolean esEntidadValida(Entidad entidad) {
        /* aplicar validaciones sobre los datos de entidad
            antes de registrarlo en bd
         */
    }
}

Как он объясняется в комментариях и как ты можешь видеть это, класс EntidadController, что является клиентом интерфейса EntidadDao, он не нуждается в том, чтобы ни узнать, ни использовать методы, которые не являются публичными в классе, который осуществляет вышеупомянутый интерфейс.

Сцена 2: Нужно использовать новое осуществление того же интерфейса. В этом случае, нужно упрощать доступ к данным.

//se implementa la misma interfaz EntidadDao
public class EntidadDaoCacheImpl implements EntidadDao {
    //clase para obtener los datos de bd y almacenarlos en cache
    private EntidadDaoBaseDatosImpl edbdi;
    //cache, para tener los datos en memoria y agilizar el acceso
    private ConcurrentHashMap<Integer, Entidad> cache;

    //se habilita DI por constructor
    public EntidadDaoCacheImpl(EntidadDaoBaseDatosImpl edbdi) {
        //inyectando el bean
        this.edbdi = edbdi;
        //creando la cache
        cache = new ConcurrentHashMap<>();
        //iniciando los valores de la cache
        List<Entidad> listaEntidades = edbdi.findAll();
        for (Entidad entidad : listaEntidades) {
            cache.put(entidad.getId(), entidad);
        }
    }

    @Override
    public void save(Entidad entidad) {
        edbdi.save(entidad);
        cache.put(entidad.getId(), entidad);
    }

    @Override
    public Entidad find(int id) {
        return cache.get(id);
    }

    @Override
    public List<Entidad> findAll() {
        return new ArrayList<>(cache.values());
    }
}

Перед этим новым осуществлением, сейчас клиент EntidadController смоги выбирать между использованием EntidadDaoBaseDatosImpl или EntidadDaoCacheImpl как осуществление для интерфейса EntidadDao чтобы использовать. Будьте классом, который выбирается, единственные значимые методы, которые клиент будет использовать, - те, что назначаются в интерфейсе. Не теряется функциональность классов, которые осуществляют интерфейс, просто это не используется прямо из-за клиента.

Сейчас, ты может появляться этот вопрос: И если я действительно нуждаюсь в том, чтобы согласиться на какой-то метод, объявленный в классе осуществления, которое не в интерфейсе? Так как ответ завись:

  • Ты можешь объявлять такой вышеупомянутый метод как часть интерфейса (это значит, что он будет public в классе, который помогает).
  • Ты можешь создавать новый интерфейс с этим очевидным методом и говорить, что класс осуществляет два интерфейса.
  • В зависимости от которого он сделал метод, ты можешь доставать это из класса и превращать это в метод utilizario в новом классе.

Не существует правильный ответ на темы как этот, нужно оценивать из-за случая и решать лучшее для ситуации, которой ты противостоишь.

5
ответ дан 24.11.2019, 14:38
  • 1
    Очень хорошие примеры! Часть конца - что-то, что tambié n я осведомлялся. рџ‘Ќрџ‘Њ – MatiEzelQ 05.04.2016, 03:18
  • 2
    @LuiggiMendoza он необходим использовать интерфейс всегда для вставки зависимостей? – x-rw 26.04.2017, 21:52
  • 3
    @x-rw лучше использовать интерфейсы, что конкретные классы, это считается хорошим prá ctica. Преимущества этого объясняются aquí. Сейчас, в Спринг в особенности, благодеянии использования интерфейсов для inyecció n зависимостей позволь ademá s использовать декораторы или proxies, чтобы добавлять функциональность способа diná слюда, не изменяя твоего có я говорю p.e. @Transactional верьте в proxy для твоего класса и добавь управление сделок способа " transparente". Я рекомендую тебе проверять proxy и декоратор –  26.04.2017, 22:04

Когда ты объявляешь зависимость от интерфейса, он, потому что у интерфейса есть все то, во что ты нуждаешься, чтобы реализовывать задание, которого ты просишь у этого интерфейса, если у класса, который осуществляет интерфейс, есть другие методы, эти должны быть неуместными для кода клиент.

В хороший рисунок интерфейс должен поставлять необходимые методы для того, чтобы клиенты класса, который будет осуществлять интерфейс, смогли реализовывать Вашу работу.

Достаточно общо, когда используется Депенденси Инхектион, что методы интерфейса соответствовали одному один с публичными методами класса, который это осуществляет, так что это не была бы должна быть проблема.

Если ты хочешь использовать добавочные методы, которые не в интерфейсе просто, добавь их к интерфейсу.

3
ответ дан 24.11.2019, 14:38

Тогда: Стой, что у объекта не было зависимостей, пока он теряет функциональность?

Нет потери функциональности, это что-то различное, интерфейс - контракт, который определяет некую функциональность, из-за определения не моги ограничивать ее.

Случается, что интерфейсы вводят комплексность в код, но эта комплексность оправдана Вашим преимуществом: Отделять функциональность осуществления.

Представь, что ты разрабатываешь приложение, ты трогаешься из-за рисунка компонентов, тогда ты определяешь интерфейсы каждого компонента и зависимостей от других интерфейсов, с этим только уже у тебя есть весь рисунок. Потом ты можешь осуществлять каждый компонент по отдельности, с Вашими унитарными доказательствами, где mockeas другие интерфейсы и доказательства каждая линия кода компонента под различными условиями, хорошо контролируемыми (из-за mocks), и просвет, потом ты можешь (или не) использовать вставку зависимостей, чтобы "монтировать кабель" система, когда ты закончил всю разработку.

За пределами интерфейсов, свойственных Java, есть серия вещей, которые стоит отделять с интерфейсами каждый раз, когда смогли:

  • Модель данных, основание, все это. Оправдание не проходит по, который, может быть, завтрашний день, ты изменил Oracle MySql. Окончательный разум состоит в том, чтобы было невозможно, из-за рисунка, включать детали доступа на основе данных внутри других модулей, делая самый чистый код "из-за рисунка". Примеры интерфейса: MiTablaN, и MiTablasProvider и POJO.

  • Сеть / диск / периферийное устройство: А именно, Выпяченный Ввод, и не возвращать объекты тип File или Socket, в этих интерфейсах возвращать и получать разгрузки ввода / вывода или сходные. Примеры: InputStream, OutputStream, Reader, MiNetworkProvider, MiFileSystemProvider.

  • Другие примеры: LOGS или реестр приложения, книжные магазины третьих (путь wrapper), REST API.

3
ответ дан 24.11.2019, 14:38