• Чем отличается Visual C# от C#?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Под C# обычно следует понимать Visual C#, точнее Microsoft Visual C#.

    Microsoft Visual C# - реализация языка C# компанией Microsoft.

    Наверное выглядит странно, учитывая что язык создан в Microsoft для Microsoft .NET Framework, но на данный момент есть две спецификации C#: ECMA-334, ISO/IEC 23270:2006 (в чем разница, не имею представления).
    Microsoft штампует новые версии C# со страшной скоростью, и как следствие выпускает новые спецификации :-)

    Из-за разных реализация C#, стандартов, юридических проблем в языке появляются искажения и не стыковки. Например, при программировании под Mono есть мелкие особенности, которые следует учитывать. А в целом, C# - он один.
    Ответ написан
  • Почему phpDocumentor не подхватывает документацию в файле?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    /**
     * @see CommonModule::onLoad()
     * CommonModule constructor. 
    * You can not change the constructor directly, but you can use onLoad function instead.
     */
    final function __construct()

    Указывать @see перед описанием (summary) неправильно.

    https://www.phpdoc.org/docs/latest/guides/docblock...

    be57b9047f6548b1822476458e094357.png
    Нужно изменить порядок, сначала должно идти summary, затем может идти description и только потом tags (@see - относится к tags):

    /**
     * CommonModule constructor. 
     * You can not change the constructor directly, but you can use onLoad function instead.
     *
     * @see CommonModule::onLoad()
     */
    final function __construct()
    Ответ написан
    Комментировать
  • Ошибка при запуске app.js - env, как исправить?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Символьную ссылку создать, выполнить в терминале следующую команду:
    ln -s /usr/bin/nodejs /usr/bin/node
    Ответ написан
    Комментировать
  • Как составить регулярное выражение исходя из следующих условий?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    В ситуациях, когда нужно изолировать фрагменты текста от обработки, проще всего перед обработкой их маскировать. Заменить на что-нибудь, что точно не попадет под обработку. А после обработки вернуть назад.

    this.replaceStandardHtmlEntities = (text) => {
      // маскируем ссылки
      text = text.replace(/\<a([^\>]*)\>/g, String.fromCharCode(1) + 'a$1' + String.fromCharCode(1));
      text = text.replace(/\<\/a\>/g, String.fromCharCode(1) + '/a' + String.fromCharCode(1));
    
      // остальной код без изменений
      let regular = /( {2}|&|'|<|>|\\n)/g, translate = {
        '  ': '&nbsp',
        '&': '&amp',
        '\'': '&quot',
        '<': '&lt',
        '>': '&gt',
        '\\n': '<br>'
      };
    
      // результат замены передаем в переменную
      let result = (text.replace(regular, (match, entity) => {
        return translate[entity];
      }));
      
      // возвращаем ссылки обратно
      result = result.replace(/\x01a([^\x01]*)\x01/g, '<a$1>');
      result = result.replace(/\x01\/a\x01/g, '</a>');
      
      return result;
    };

    Смотреть в JSFiddle
    Ответ написан
    5 комментариев
  • PHP: imagedestroy и unset - в чём отличия?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Разница может быть в работе методов освобождения ресурсов (объект может быть сложным) и в механизмах работы сборщика мусора. Особенно, если учесть, что GD - это отдельный модуль, а unset является частью ядра PHP.

    В документации про возможность использования unset вместо imagedestroy ничего не видно. Так что лучше использовать imagedestroy, чтобы не было утечек памяти.

    На этот вопрос наверное только разработчики PHP смогут ответить и то не всякий, кто участвовал в разработке. Версий PHP уже было много, что-то в процессе могло поменяться в логике. Полной картиной только старожилы могут обладать, которые участвовали в разработке всех версий PHP и находятся в здравом уме и твердой памяти :-)

    Исходный код открыт, можно в нем поискать :-)
    Ответ написан
    Комментировать
  • Как правильно удалить часть строки?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Раз подстроки разделены запятыми, то проще всего получить массив с помощью функции split, перебрать элементы циклом, собрать новый массив без ненужных данных и потом склеить с помощью join:

    // входящие данные
    var str = 'odborka_vecher_01.jpg,%,podborka_vecher_37.jpg';
    // разбиваем на массив по запятой
    var arr = str.split(',');
    // фильтруем
    var resultArr = [];
    for (var i = 0; i < arr.length; i++)
    {
      if (arr[i] != '%')
      {
        // добавляем в результат
        resultArr.push(arr[i]);
      }
    }
    // склеиваем новый массив в строку
    var result = resultArr.join(',');
    // выводим полученный результат в консоль
    console.log(result);

    Более короткий вариант и без join:

    str.split(',').forEach(function(item) {
      if (item != '%') { 
        if (result != '') { result += ','; }
        result += item; 
      }
    });
    
    console.log(result);

    Еще короче, с использованием filter:

    var arr = str.split(',').filter(function(item) {
      return item != '%';
    });
    
    var result = arr.join(',');

    В качестве альтернативного решения, обычный поиск и замена:

    var result = str.replace(/,\%,/g, '');
    // с учетом того, что % может быть первым или последним
    // str.replace(/(\,*)\%(\,*)/g, '')
    Ответ написан
  • Как взять токен из адресной строки (oauth)?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Вы должны сохранить полученный маркер доступа (access token). В случае с JavaScript это можно сделать в sessionStorage (в рамках текущей сессии) или localStorage (сохранить на длительный срок), либо передать на сервер (более надежный вариант).

    Код сохранения маркера доступа в хранилище браузера может быть примерно таким:
    if (window.location.hash != '') {
      var hash = window.location.hash.substring(1);
      var accessToken = hash.substr(hash.indexOf('access_token=')).split('&')[0].split('=')[1];
      sessionStorage.setItem('access_token', accessToken);
      // для безопасности, из url лучше удалить access_token
      window.location.hash = '';
      // window.location.href = window.location.href.substr(0, window.location.href.indexOf('#'))
    }
    
    // и далее использовать сохраненный маркер доступа
    var currentAccessToken = sessionStorage.getItem('key');
    Ответ написан
    Комментировать
  • Почему не создаётся процедура?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Все правильно, нельзя так извращаться :-)

    Если очень хочется, то можно сделать динамический запрос и выполнить через EXEC . Примерно так:

    DECLARE @query AS nvarchar(1024);
    
    SET @query = N'CREATE VIEW WomanAward AS 
    SELECT * FROM [Сотрудники] 
    WHERE [Пол] = ''Ж'' AND [Премия] BERWEEN 130 AND 210';
    
    EXEC sp_executesql @query;
    -- EXEC(@query);

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

    См. также:
    - EXECUTE
    - sp_executesql
    Ответ написан
  • Как правильно подключить jquery к сборке?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Надеюсь, в app.js сделан require('jquery') :-)

    В plugins можно попробовать добавить ProvidePlugin, с указанием альтернативных имен jQuery, чтобы webpack эти имена обрабатывал как jQuery:

    plugins: [
      new webpack.EnvironmentPlugin('NODE_ENV'),
    
      new webpack.ProvidePlugin({
        '$': 'jquery',
        'jQuery': 'jquery'
      })
    ]

    В alias указывать jQuery нет необходимости.

    А вот добавление node_modules в modulesDirectories может помочь:

    resolve: {
      modulesDirectories: ['./', 'node_modules'],
      // ...
    }
    Ответ написан
    Комментировать
  • Как задать множество переменным одинаковое значение?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Для этого проще массивы использовать:

    $per = [0, 1, 2, 3, 4, 5];
    
    for ($i = 0; $i < count($per); $i++) 
    {
      $per[$i] = 0;
    }

    Если массив нужно просто обнулить, то проще удалить его (unset($per)) и создать новый.

    Если хочется по извращаться с именами переменных, то можно так:

    // предполагается наличие переменных $per1, $per2 ... $per24
    // если переменных в наличии не будет, то они будут созданы :-)
    for ($i = 1; $i <= 24; $i++) 
    {
      ${'per'.$i} = 0;
    }
    Ответ написан
    Комментировать
  • Как сделать конструктор класса правильно?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Обычно это делается в отдельном методе, наподобие Save.

    У пользователя, которого нет в базе, $this->id будет нулевым.

    И для загрузки данных лучше сделать отдельный метод - Load.

    public function __construct($id = NULL)
    {
      $this->id = $id;
      $this->Load();
    }
    
    public function Load()
    {
      // идентификатор не указан, загружать данные не нужно
      if (!isset($this->id) || (int)$this->id == 0)
      {
        return false;
      }
    
      // указан идентификатор, загружаем данные в текущий экземпляр класса
      $mysqli = db_connect();
    
      $req = 'SELECT u.ID, u.name, u.age, u.city_id, c.city_name FROM users AS u '.
             'INNER JOIN cities AS c ON (c.city_id = u.city_id) '.
             'WHERE u.ID = '.$this->id; // опасно так передавать в запрос данные
    
      $res = $mysqli->query($req);
    
      if ($res !== false){
        if ($res->num_rows == 1 )
        {
          // смысла в цикле нет, ожидается ведь одна строка данных
          while ($row = $res->fetch_assoc())
          {
            $this->id = $row['ID'];
            $this->username =  $row['name'];
            $this->city_name = $row['city_name'];
            $this->city_id = $row['city_id'];
            $this->age = $row['age'];
          }
        }
        else
        {
          return false;
        }
      }
      else
      {
        return false;
      }
    }
    
    public function Save()
    {
      $sql = '';
      $params = [];
    
      if (!isset($this->id) || (int)$this->id == 0)
      {
        // запрос на добавление данных в базу
        $sql = 'INSERT INTO users (name, city_id, age) VALUES (?, ?, ?)';
        // параметры запроса
        $params = [$this->username, $this->city_id, $this->age];
      }
      else
      {
        // запрос на обновление данных в базе
        $sql = 'UPDATE users SET name = ?, city_id = ?, age = ? WHERE ID = ?';
        // параметры запроса
        $params = [$this->username, $this->city_id, $this->age, $this->id];
      }
    
      // код выполнения запроса
      // не могу быть уверен в коде, давно не писал и проверять лень
      // должно быть что-то типа такого:
    
      $mysqli = db_connect(); // лучше сделать отдельный метод для создания и закрытия соединений
    
      // подготавливаем запрос
      $s = $mysqli->prepare($sql);
    
      // выполняем запрос с указанными параметрами
      $s->execute($params);
    
      // тут еще неплохо бы закрыть все соединения :-)
    }

    Можно пойти дальше и сделать базовый класс, который будет содержать готовые методы Load и Save.
    Либо сделать отдельный класс, который будет получать данные и заполнять экземпляры классов этими данными, сохранять и удалять данные. В таком случае классы таблиц (сущности) будут просто описывать структуру данных в базе и не будут выполнять никаких действий. Этот вариант мне нравится больше, т.к. он чище (проще) и более гибкий.

    Придется придумать, как сопоставлять данные, если имена свойств не соответствую именам полей в базе. Скорее всего интерфейсы нужно будет использовать. Также учесть свойства, которые не определены в базе (которые следует игнорировать).

    Для работы со структурой классов потребуется использовать Reflection.
    В общих чертах примерно следующим образом:

    // создаем экземпляр класса User
    $instance = new User();
    
    // получаем информацию об экземпляре класса
    $r = new \ReflectionClass($instance);
    
    // получаем список публичных свойств
    $propeties = $r->getProperties(\ReflectionProperty::IS_PUBLIC);
    
    // перебираем все свойства
    foreach ($propeties as $property)
    {
      // получаем имя свойства
      $propertyName = $property->getName();
      // получаем значение свойства
      $propertyValue = $property->getValue();
      // и далее можно использовать полученную информацию
      // для формирования динамических запросов к базе
    
      // полученные из базы данные можно передать в нужные свойства
      $property->setValue("значение из базы");
    }

    Готовое решение найти будет проще :-)
    Ответ написан
    1 комментарий
  • Как вырезать из строки слово?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Разобрать URL можно с помощью функции parse_url.
    Получить отдельные параметры строки запроса можно методом parse_str.

    $url = 'http://blog.des/?go=category&q=backend&page=2';
    // в элементе с именем query будет строка параметров запроса
    // go=category&q=backend&page=2
    $query = parse_url($url)['query'];
    // функция parse_str разберет строку параметров запроса
    // и передаст ассоциативный массив в переменную $params
    parse_str($query, $params);
    // выводим значение параметра page
    echo $params['page'];

    Либо можно сразу в parse_str передавать URL:

    $url = 'http://blog.des/?go=category&q=backend&page=2';
    parse_str($url, $params);
    echo $params['page'];

    Если вы хотите получить значение параметра для текущей страницы, то проще это делать через $_GET:

    echo 'Текущая страница: '.$_GET['page'];

    Удалить параметр из строки запроса можно просто собрав новую строку, опустив не нужные параметры. Для текущего адреса примерно так:

    $queryString = [];
    
    foreach ($_GET as $key => $value) {
      // если параметр page, пропускаем его
      if ($key == 'page') { continue; }
      // остальные добавляем в queryString
      $queryString[$key] = $value;
    }
    
    // собираем массив новых параметров в строку параметров запроса
    $queryString = http_build_query($queryString);
    
    // новые параметры
    echo $queryString;

    Получив новую строку параметров, можно сформировать новый URL и выполнить перенаправление на него:

    $url = "http://blog.des/";
    
    if ($queryString != '')
    {
      $url .= "?".$queryString;
    }
    
    // это следует делать до первого вывода, иначе могут быть ошибки/предупреждения
    header('HTTP/1.1 301 Moved Permanently');
    header('Location: '.$url);
    Ответ написан
    2 комментария
  • Как добавить php код в страницу html?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Получить в переменную обработанное содержимое PHP файла можно примерно так:
    <?php
    function GetContents($path)
    {
      ob_start();
      require($path);
      $result = ob_get_contents();
      ob_end_clean();
    
      return $result;
    }
    
    $jumi1= GetContents('admin/jumi/jumi1.php');
    ?>

    Если открыть код, то есть строчка <? include('admin/jumi/jumi1.php'); ?>.
    Но она не отображается при открытии страницы и php не выполняется.

    Если вообще никакой код не работает, то вероятно короткая форма записи (<? ?>) тегов PHP не включена. Нужно использовать полную форму:

    <?php include('admin/jumi/jumi1.php'); ?>

    Либо проблема с путями:

    <?php include($_SERVER['DOCUMENT_ROOT'].'/admin/jumi/jumi1.php'); ?>

    Если есть доступ к журналу, в нем можно узнать подробности проблемы.

    Если вы пытаетесь заставить работать код PHP на странице HTML (*.html), то необходимо настроить веб-сервер, чтобы он файлы с расширением .html обрабатывал как файлы .php, но лучше так не делать и ограничиться использованием PHP в файлах .php.
    Ответ написан
    Комментировать
  • Как создавать и отображать элементы в одном представлении (представление строго типизировано)?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Нужно создать новую модель, которая будет включать в себя IEnumerable<City> и City.

    Примерно так:
    public class NewModel
    {
    
      public IEnumerable<City> CitiesList { get; set; }
    
      public City City { get; set; }
    
    }

    Использовать:
    @model NewModel

    @foreach(var city in Model.CitiesList)
    {
      // список
    }
    
    @Model.City.КакоетоСвойство

    Для удобства можно сделать частичные представления и передавать в них CitiesList и City. Первое представление должно ожидать IEnumerable<City>, второе - City. Т.е. будет примерно то, что у вас уже есть, просто обычные представления нужно сделать частичными (убрать все, что связано с шаблонизацией). Вывести эти представления в общем представлении, с передачей в их модель значений из модели общего представления (NewModel):

    @model NewMode
    
    @Html.Partial("CitiesList", Model.CitiesList)
    
    @Html.Partial("CityEditor", Model.City)

    Частичное представление CitiesList:

    @model IEnumerable<City>
    
    // ...

    Частичное представление CityEditor:

    @model City
    
    // ...
    Ответ написан
    2 комментария
  • Как убрать экранирование с помощью angular?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Поиск и замена?
    return $sce.trustAsHtml(text.replace(/\\/g, ''));
    Можно отдельный фильтр для этого сделать :-)

    Набросал пример, у меня слеш и так не выводится:

    <div ng-app="app">
      <div ng-controller="ExampleController">
        <p ng-bind-html="text | trusted"></p>
      </div>
    </div>

    angular.module('app', []).controller('ExampleController', function($scope) {
      $scope.text = "A GREAT LOT, THIS IS THE ONE YOU\'VE BEEN LOOKING FOR";
    }).filter('trusted', ['$sce', function ($sce) {
      return function (text) {
        return $sce.trustAsHtml(text); // $sce.trustAsHtml(text.replace(/\\/g, ''));
      };
    }]);
    Ответ написан
    Комментировать
  • Как по клику заменить слово на другое и при повторном клике обратно?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    <div class="clickme">
    Нажми на меня!
    </div>

    $('.clickme').click(function(){
      if (!$(this).data('status')) {
        $(this).html('Молодец! А еще раз слабо?');
        $(this).data('status', true);
      }
      else {
        $(this).html('Ну ты гигант! А может еще раз?');
        $(this).data('status', false);
      }
    });

    https://jsfiddle.net/alekseynemiro/bu0zfgz4/
    Ответ написан
    1 комментарий
  • Как вставить php в путь ссылки?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Лучше относительно корневого каталога делать:
    <link rel="stylesheet" href="/main.css">
    Ответ написан
    Комментировать
  • Как добавить web проект в VS?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    добавляются только файлы корневого каталога

    Не совсем понял, о чем речь. Возможно, в Visual Studio 2015, если делать вставку из проводника, файлы могут не включаться в решение, будут добавлены только каталоги. Их придется включать вручную. Для этого нужно включить показ всех файлов:

    89d8e00e769e41808eab50484b01860e.png
    И затем выбрать недостающие файлы и папки и включить их в проект через контекстное меню:

    110986bb5b81446e980dce674c107b4f.png

    В Visual Studio для PHP я использую PHP Tools for VS.

    С TypeScript в рамках Visual Studio проект PHP дружить так и не захотел.

    Самое простое решение - сделать отдельный проект для TypeScript (в рамках одного решения).

    В bundleconfig.json (BundlerMinifier) можно прописать вывод собранных файлов в любую папку, в том числе в папку с проектом PHP.

    Вот тут можно посмотреть пример, как это может быть.

    460884d980314ab4902ecdd101ad5888.png
    Структура решения:
    /Решение
    -- FrontEndScripts - проект TypeScript
    -- SmallServerAdmin - проект PHP, в который, помимо прочего, собираться клиентский код

    bundleconfig.json
    [
      {
        "includeInProject": false,
        // вывод js делаем в проект SmallServerAdmin
        "outputFileName": "../SmallServerAdmin/Content/compiled.js",
        "inputFiles": [
          // файлы, которые должны входить итоговую в сборку
          "libs/jquery-1.11.2.js",
          "libs/jquery-ui.min.js",
          "libs/jquery.json.min.js",
          "libs/jquery.caret.min.js"
          // ...
        ]
      }
    ]


    TypeScript компилируется в JavaScript в тоже место, где находятся файлы TypeScript. Каждый файл прописывается в bundleconfig.json. Затем все это собирается в итоговый файл /SmallServerAdmin/Content/compiled.js.

    Плохое, но простое решение.

    Еще можно попробовать в настройках проекта TypeScript (Проект -> Свойства проекта) указать параметры сборки и папку вывода (путь может быть относительным).

    ae17a55c4ba748fb9eb97d8974479ee9.png
    Если оставаться в рамках одного проекта, то можно попробовать сделать файл конфигурации TypeScript в корне проекта: tsconfig.json. Возможно он будет использован, но в проектах отличных от TypeScript я этого не проверял и не могу точно сказать, как это будет работать.

    {
      "compilerOptions": {
        "outDir": "./куда делать вывод компилированных файлов",
        "sourceMap": false,
        "noImplicitAny": true,
        "module": "CommonJS",
        "target": "es5"
      },
      "files": [
        // файлы проекта
        "./init.ts"
      ]
    }


    С WebPack можно сделать больше, но это будет существенно сложнее. Пример решения (TypeScript + PHP) можно тут посмотреть.

    Для WebPack потребуется:

    • Node.js
    • Webpack Task Runner
    • NPM Scripts Task Runner

    Управление сборкой TypeScript можно оставить студии, это проще всего. А все остальное прописывается в webpack.config.js. Но все может быть очень сложно, на получение оптимальной конфигурации можно угрохать пару десятков часов :-)
    Ответ написан
    2 комментария
  • Как задать динамическое значение props?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Использовать state вместо props:
    var Hello = React.createClass({
      getInitialState: function() {
        return { test: 0 };
      },
      componentWillMount: function() {
        setInterval(() => {
          var dynamicData = (Math.random() * 100).toFixed(0);
          this.setState({ test: dynamicData });
        }, 3000);  
      },
      render: function() {
        return <h2>{this.state.test}</h2>;
      }
    });
    
    ReactDOM.render(
      <Hello />,
      document.getElementById('container')
    );

    Если нужны именно props, то родительский компонент может менять состояние и передавать его в нужное свойство дочернего компонента, а дочерний компонент может обрабатывать изменения в componentWillReceiveProps(nextProps).

    Еще можно вызывать forceUpdate(), но лучше так не делать.

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

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Интерфейсы использую, если это необходимо :-) Как я понимаю, что это необходимо, сказать сложно. Все зависит от задачи. Если рассматривать вопрос с позиции создания интерфейсов, то иногда это может быть очевидно, а иногда приходится подумать, стоит использовать интерфейсы или нет. Пихать их где попало - плохая идея.

    Готовые интерфейсы, да, часто используются. Самым популярным в .NET наверное будет IDisposable :-)

    Из общедоступных практических примеров использования собственных интерфейсов:

    • Простой интерфейс, описывающий всего один метод.

      interface ILoginForm
      {
      
        void WebDocumentLoaded(System.Windows.Forms.WebBrowser webBrowser, Uri url);
      
      }


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

      if (typeof(ILoginForm).IsAssignableFrom(this.GetType()))
      {
        this.CanLogin = false;
        Debug.WriteLine("ILoginForm", "LoginForm");
        ((ILoginForm)this).WebDocumentLoaded(this.webBrowser1, e.Url);
      }


    • Вот другой пример интерфейса.

      export interface ILocalization {
      
        Loading: string;
      
        LoadingFileContents: string;
      
        // ...
      }

      Файлы с ресурсами локализации реализуют этот интерфейс.

      export class RU implements ILocalization {
      
        public Loading: string = 'Загрузка...';
      
        public LoadingFileContents: string = 'Получение содержимого файла...';
      
        // ...
      }

      Это просто упрощает работу с кодом и больше ничего.

      6d84933efd7246e6adbeeed114ef8bbe.png

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


    • Еще пример интерфейса для вспомогательных классов работы с базами данных. Он просто описывает, каким должен быть класс.

      interface IDBClient
      {
          
        public function ExecuteNonQuery();
        public function ExecuteScalar();
        public function GetRow();
        // ...
      
      }

      А толку от этого никакого.

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

    • А вот тут я реализовал множество готовых интерфейсов, чтобы получить нужные свойства и поведение класса. Например, интерфейс IConvertible позволяет адекватно реагировать на Convert.To*. Интерфейс ISerializable позволяет описать правила сериализации экземпляра класса. И т.п.

      // реализация метода ToInt32
      public int ToInt32(IFormatProvider provider)
      {
        // если пусто, возвращаем ноль
        if (!this.HasValue) { return 0; }
        // если что-то есть, извлекаем числа и пробуем вернуть int32
        return Convert.ToInt32(OAuthUtility.GetNumber(this.Data));
      }


    • Вот пример, где можно было использовать интерфейсы, но я принял решение в пользу базовых классов (на раннем этапе). Некоторые поставщики OAuth позволяют обновлять маркер доступа и/или отзывать его, но не все. В базовом классе нижнего уровня для определениях этих особенностей я сделал два свойства, причем даже не абстрактных.

      public bool SupportRevokeToken { get; protected set; }
      public bool SupportRefreshToken { get; protected set; }

      Клиенты, которым нужны эти свойства, задают их в конструкторе, выглядит это так.

      public GoogleClient(string clientId, string clientSecret) : base(/*...*/)
      {
        // ...
        base.SupportRevokeToken = true;
        base.SupportRefreshToken = true;
      }

      Не очень красиво, но ошибкой это назвать нельзя. Если бы я использовал интерфейсы, то для отзыва и обновления маркера мне бы пришлось делать два разных интерфейса, в каждом по одному bool свойству, либо пустых (но с явно определенным свойством для других программистов все было бы очевидней; это тоже спорный вопрос). Можно было пойти дальше и измельчить на интерфейсы другие особенности, но это было бы плохим решением, т.к. программистам пришлось бы указывать портянку интерфейсов, а потом писать много кода. С базовыми классами код писать не нужно, даже не нужно вникать в то, как это работает; в большинстве случаев достаточно просто выполнить простую конфигурацию дочернего класса в конструкторе. Но я не могу утверждать на 100%, что это лучшее решение для данного случая.

    • Пример, где ReactJS и TypeScript заставляют клепать интерфейсы и я этому совсем не рад.

      export interface ILoginState {
      
        Username?: string;
        Password?: string;
      
        // ...
      
      }
      
      export default class Index extends Page<any, ILoginState> {
      
        constructor() {
      
          this.state = {
            Username: '',
            Password: ''
          };
      
        }
      
        // ...
      
      }

      Можно забить и использовать анонимные типы, но для порядка и удобства работы с кодом придется открывать собственный завод по производству интерфейсов :-)

    Ответ написан
    Комментировать