Использование области запроса для хранения объекта

Я использую CFWheels для разработки приложения в ColdFusion.

У меня есть модель под названием Vote.cfc. Прежде чем объект Vote можно будет создать, обновить или удалить, мне нужно получить объект сообщения из другой модели: Post.cfc. Голос принадлежит сообщению. Пост имеет много голосов.

Используя данные из объекта post, мне нужен объект to validate the vote по нескольким критериям и нескольким функциям. Единственный способ, которым я могу думать о сохранении объекта сообщения, чтобы он был доступен для этих функций, - это сохранить его в области запроса.

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

Моя другая альтернатива — загружать новый экземпляр объекта post в каждую функцию, которая его требует. Хотя Wheels использует кэширование, это привело к увеличению времени запросов на 250%.

ОБНОВИТЬ

Вот несколько образцов. Во-первых, контроллер проверяет, существует ли уже объект голосования. Если да, то удаляет, если нет, то создает. Функция контроллера по сути является функцией переключения.

Контроллер голосов.cfc

   private void function toggleVote(required numeric postId, required numeric userId)
    {
    // First, we look for any existing vote
    like = model("voteLike").findOneByPostIdAndUserId(values="#arguments.postId#,#arguments.userId#");

    // If no vote exists we create one
    if (! IsObject(like))
    {
        like = model("voteLike").new(userId=arguments.userId, postId=arguments.postId);
    }
    else
    {
        like.delete()
    }           
}

Модель VoteLike.cfc

После этого перед проверкой срабатывает обратный вызов, зарегистрированный в модели. Он вызывает функцию, которая извлекает объект записи, которому будет принадлежать голос. Функция getPost() сохраняет сообщение в области запроса. Теперь он доступен для множества функций проверки в модели.

// Load post before Validation via callback in the Constructor
beforeValidation("getPost");

private function getPost()
{
    // this.postId will reference the post that the vote belongs to
    request.post = model("post").findByKey(this.postId);
}

// Validation function example
private void function validatesIsNotOwnVote()
{
    if (this.userId == request.post.userId)
    {
       // addError(message="You can't like your own post.");
    }
}

Альтернативой функции getPost() является использование вызова с областью действия "this.post().userId" для получения объекта сообщения как такового:

private void function validatesIsNotOwnVote()
{
    if (this.userId == this.post().userId)
    {
        addError(message="can't vote on your own post")
    }
}

Но тогда мне пришлось бы повторять этот ограниченный вызов this.post().userId для КАЖДОЙ функции, что, как я думаю, замедляет запрос!


person Mohamad    schedule 02.02.2011    source источник
comment
@Mel: основываясь на ответе @Daniel Short и комментариях ниже, можете ли вы дать некоторые разъяснения о том, как устроен ваш код?   -  person orangepips    schedule 03.02.2011
comment
@orangepips, я обновил свой вопрос примером кода!   -  person Mohamad    schedule 03.02.2011
comment
Вся проверка выполняется внутри VoteLike.cfc? Если это так, то вы можете просто установить Variables.post = model(post).findByKey(this.postId), а затем ссылаться на Variables.post вместо request.post.   -  person Dan Short    schedule 03.02.2011
comment
@Daniel, на самом деле нет, мои функции проверки находятся в базовой модели Vote.cfc. VoteLike.cfc и VoteDislike.cfc используют наследование и расширяют Vote.cfc. Поскольку они используют одну и ту же проверку, они ссылаются на функции проверки в Vote.cfc. Кроме того, я думал, что область переменных не является потокобезопасной?   -  person Mohamad    schedule 03.02.2011
comment
Только что увидел вашу другую правку. Используйте область переменных, чтобы сделать переменную доступной для всего CFC.   -  person Dan Short    schedule 03.02.2011
comment
Что касается твоего наследства, это звучит прекрасно. Попробуйте variable.Post вместо Request или This.post. Вам может понадобиться (и я не уверен в этом на 100%) установить Variables.Post в псевдоконструкторе, чтобы убедиться, что он доступен для всего CFC.   -  person Dan Short    schedule 03.02.2011
comment
@Daniel, я думал, что область переменных не является потокобезопасной ... я что-то упустил?   -  person Mohamad    schedule 03.02.2011
comment
Область переменных внутри CFC так же безопасна для потоков, как и свойства CFC. Это означает, что если CFC не является общим и изменяется в рамках одного запроса, то нет проблем с использованием области переменных внутри самого CFC. Область видимости переменных внутри CFC не будет просачиваться за пределы самой CFC.   -  person Dan Short    schedule 03.02.2011
comment
@ Даниэль, спасибо за разъяснение. Если я использую наследование, будет ли область переменных доступна только для базовой модели или также для любых моделей, которые ее расширяют? Итак, если я установлю variable.post в Vote.cfc, будет ли он доступен в VoteLike.cfc, который расширяет Vote.cfc?   -  person Mohamad    schedule 03.02.2011
comment
Да, это будет. Область переменных доступна от самого верха до самого низа стека наследования.   -  person Dan Short    schedule 03.02.2011
comment
@Mel: если предположить, что VoteLike передается, а его метод getPost() доступен другим объектам (например, Vote), @Dan Short получил правильный ответ здесь, в комментариях.   -  person orangepips    schedule 03.02.2011
comment
Рассматривая возможность пересмотра всего моего ответа ниже на основе того факта, что Мел уже использовал композицию, чтобы собрать свои объекты воедино, и я предположил, что это не тот случай, когда я писал свой ответ.   -  person Dan Short    schedule 03.02.2011
comment
Все сделано. Я оставил исходный ответ, так как в нем все еще есть полезная информация о том, как объекты передаются поRef вместо byVal.   -  person Dan Short    schedule 03.02.2011


Ответы (2)


Обновить с новым ответом на основе ветки комментариев в OP

Поскольку вы расширяете базовый объект Vote из формы VoteLike.cfc, CFC совместно используют локальную область потоковобезопасных переменных (пока вы не сохраняете ее в области приложения или сервера, где с ней могут возиться другие). Это означает, что вы устанавливаете значение, такое как Variables.Post, один раз и ссылаетесь на него в любой функции в стеке CFC.

Поэтому измените свою функцию getPost() на это:

beforeValidation("getPost");

private function getPost(){
    Variables.Post = model("post").findByKey(this.postID);
}

Теперь внутри любой функции в VoteLike.cfc вы можете ссылаться на Variables.Post. Это означает, что у вас есть один экземпляр Post в области локальных переменных CFC, и вам не нужно передавать его через аргументы или другие области.

Оригинальный ответ ниже

Самый правильный способ справиться с этим — передать объект каждой отдельной функции в качестве аргумента. Это или добавьте Post как свойство объекта Vote, чтобы объект Vote имел доступ к полному объекту Post.

<cffunction name="doTheValidation">
    <cfargument name="ThePost" type="Post" required="true" />
    <cfargument name="TheVote" type="Vote" required="true" />
    <!--- do stuff here --->
</cffunction>

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

Гораздо лучше передавать объект по кругу или использовать композицию для объединения объектов таким образом, чтобы они знали друг о друге.

Обновлять

Что касается проблем с производительностью, если вы используете композицию, чтобы связать свои объекты вместе, вы получите всего два объекта в памяти, поэтому вам не нужно создавать кучу ненужных объектов Post. Кроме того, передача CFC в функцию в качестве аргумента будет передавать CFC по ссылке, что означает, что не создается дубликат CFC в памяти, что также должно заботиться о ваших проблемах с производительностью.

Пример обновления кода

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

TestObject.cfc:

<cfcomponent output="false">
    <cfproperty name="FirstName" displayname="First Name" type="string" />
    <cfproperty name="LastName" displayname="Last Name" type="string" />

</cfcomponent>

index.cfm:

<!--- Get an instance of the Object --->
<cfset MyObject = CreateObject("component", "TestObject") />

<!--- Set the initial object properties --->
<cfset MyObject.FirstName = "Dan" />
<cfset MyObject.LastName = "Short" />

<!--- Dump out the object properties before we run the function --->
<cfdump var="#MyObject#" label="Object before passing to function" />

<!--- Run a function, sending the object in as an argument, and change the object properties --->
<cfset ChangeName(MyObject) />

<!--- Dump out the object properites again, after we ran the function --->
<cfdump var="#MyObject#" label="Object after passing to function" />

<!--- Take a TestObject, and set the first name to Daniel --->
<cffunction name="ChangeName">
    <cfargument name="TheObject" type="TestObject" required="true" hint="" />
    <cfset Arguments.TheObject.FirstName = "Daniel" />
</cffunction>

Вы заметите, что при запуске index.cfm в первом дампе FirstName имеет значение Dan, а во втором — Daniel. Это связано с тем, что CFC передаются функциям по ссылке, а это означает, что любые изменения, сделанные внутри этой функции, применяются к исходному объекту в памяти. Следовательно, нет воссоздания объектов, поэтому производительность не снижается.

Дэн

person Dan Short    schedule 02.02.2011
comment
Как это решает проблему производительности, поднятую в вопросе? - person orangepips; 02.02.2011
comment
Потому что вы не создаете дополнительные экземпляры объектов, как указал первоначальный постер в качестве своего обходного пути, чтобы не использовать область запроса. Вы либо используете композицию и все еще имеете дело только с двумя объектами, либо вы передаете объекты в функции, которые все еще не создают экземпляр нового объекта. - person Dan Short; 02.02.2011
comment
Кратко: мое прочтение вопроса - это несколько шаблонов/CFC в запросе, вызывающем некоторый метод поиска (например, getPost()). Если я что-то не упустил, подход этого ответа требует рефакторинга клиентского кода для передачи по этому объекту Post - вероятно, у фреймворка есть какая-то оболочка для этой цели - вместо нескольких вызовов getPost(). Так что да, однократное создание экземпляра — хорошая идея, но масштабный рефакторинг — не очень. - person orangepips; 03.02.2011
comment
Согласен, придется провести некоторый рефакторинг кода, так как он уже работает не очень хорошо :), надеюсь (учитывая новый пример кода), это так же просто, как просто рефакторинг в VokeLike.cfc. - person Dan Short; 03.02.2011
comment
Short и @orangepips, я только что добавил пример выполнения вызова модели поста в нижней части моих примеров. Процесс, который делает запросы страниц длинными. - person Mohamad; 03.02.2011

request — это глобальная область, ограниченная запросом страницы (т. е. потокобезопасная), которая существует на протяжении всего запроса страницы. Так что это плохая практика, так же как и глобальные переменные - плохая практика, но недолговечная. Я думаю об этом как о подбрасывании данных в воздух или через забор в ситуации, которую вы описали, когда любой другой код может просто вырваться из воздуха.

Так что для вашей ситуации это, вероятно, просто отлично - добавьте какую-нибудь полезную ссылку в точке потребления, возможно, о том, где данные помещаются в область request в первую очередь. Тем не менее, если вы каждый раз возвращаетесь к одному и тому же исходному методу, рассмотрите возможность кэширования внутри любой функции, ответственной за создание в нем этого объекта (например, getVote()). И вы можете использовать для этого область запроса как таковую:

<cfparam name="request.voteCache" default="#structNew()#"/>
<cffunction name="getVote" output="false" access="public" returntype="any">
    <cfargument name="voteId" type="string" required="true"/>
    <cfif structKeyExists(request.voteCache, arguments.voteId)>
        <cfreturn request.voteCache[arguments.voteId]>
    </cfif>

    <!--- otherwise get the vote object --->
    <cfset request.voteCache[arguments.voteId] = vote>
    <cfreturn vote>
</cffunction>

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

person orangepips    schedule 02.02.2011